簡體   English   中英

如何在 x86-64 Linux 上的 GAS 程序集中通過 GOT 訪問 C 全局變量?

[英]How to access a C global variable through GOT in GAS assembly on x86-64 Linux?

我的問題

我正在嘗試編寫一個共享庫(不是可執行文件,所以請不要告訴我使用-no-pie )與單獨文件中的程序集和 C(不是內聯程序集)。

我想通過匯編代碼中的全局偏移表訪問 C 全局變量,因為調用的函數可能在任何其他共享庫中定義。

我知道 PLT/GOT 的東西,但我不確定如何告訴編譯器為鏈接器正確生成重定位信息(語法是什么),以及如何告訴鏈接器使用該信息實際重定位我的代碼(什么是是鏈接器選項)。

我的代碼編譯時出現鏈接錯誤

/bin/ld: tracer.o: relocation R_X86_64_PC32 against
/bin/ld: final link failed: bad value

此外,如果有人可以分享一些有關搬遷的 GAS 組件的詳細文檔,那就更好了。 例如,關於如何使用 GNU 匯編器在 C 和匯編之間進行插值的詳盡列表。

源代碼

編譯 C 和匯編代碼並將它們鏈接到一個共享庫中。

# Makefile
liba.so: tracer2.S target2.c
    gcc -shared -g -o liba.so tracer2.S target2.c
// target2.c
// NOTE: This is a variable, not a function.
int (*read_original)(int fd, void *data, unsigned long size) = 0;
// tracer2.S
.text
    // external symbol declarition
    .global read_original
read:
  lea read_original(%rip), %rax
  mov (%rax), %rax
  jmp *%rax

期望與結果

我希望鏈接器愉快地鏈接我的目標文件,但它說

g++ -shared -g -o liba.so tracer2.o target2.c -ldl
/bin/ld: tracer.o: relocation R_X86_64_PC32 against
/bin/ld: final link failed: bad value
collect2: error: ld returned 1 exit status
make: *** [Makefile:2: liba.so] Error 1

並注釋掉該行

// lea read_original(%rip), %rax

使錯誤消失。

解決方案。

    lea read_original@GOTPCREL(%rip), %rax

關鍵字GOTPCREL將告訴編譯器這是到 GOT 表的與 PC 相關的重定位。 鏈接器將計算從當前rip到目標 GOT 表條目的偏移量。

你可以驗證

$ objdump -d liba.so
    10e9:       48 8d 05 f8 2e 00 00    lea    0x2ef8(%rip),%rax        # 3fe8 <read_original@@Base-0x40>
    10f0:       48 8b 00                mov    (%rax),%rax
    10f3:       ff e0                   jmpq   *%rax

感謝彼得。

一些可能相關或不相關的信息

1. 我可以調用 C 函數
$ objdump -d liba.so
...
0000000000001109 <read1>:
    1109:       e8 22 ff ff ff          callq  1030 <read@plt>
    110e:       ff e0                   jmpq   *%rax

objdump顯示它調用了正確的 PLT 條目。

 $ objdump -d liba.so ... 0000000000001109 <read1>: 1109: e8 22 ff ff ff callq 1030 <read@plt> 110e: ff e0 jmpq *%rax
2.我可以lea正確PLT條目地址

0xffffff23 是 -0xdd,0x1109 - 0xdd = 102c

$ uname -a
Linux alex-arch 5.2.6-arch1-1-ARCH #1 SMP PREEMPT Sun Aug 4 14:58:49 UTC 2019 x86_64 GNU/Linux

環境

  • Arch Linux 20190809
 $ uname -a Linux alex-arch 5.2.6-arch1-1-ARCH #1 SMP PREEMPT Sun Aug 4 14:58:49 UTC 2019 x86_64 GNU/Linux
 $ gcc -v Using built-in specs. COLLECT_GCC=/bin/gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/9.1.0/lto-wrapper Target: x86_64-pc-linux-gnu Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --enable-multilib --disable-werror --enable-checking=release --enable-default-pie --enable-default-ssp --enable-cet=auto Thread model: posix gcc version 9.1.0 (GCC)
 $ ld --version GNU ld (GNU Binutils) 2.32 Copyright (C) 2019 Free Software Foundation, Inc. This program is free software; you may redistribute it under the terms of the GNU General Public License version 3 or (at your option) a later version. This program has absolutely no warranty.

顯然,鏈接器對 ELF 共享對象中的符號強制執行全局可見性與隱藏可見性,不允許“后門”訪問參與符號插入的符號(因此可能超過 2GB。)

要使用正常的 RIP 相對尋址直接從同一共享對象中的其他代碼訪問它,請通過設置其 ELF 可見性來隱藏符號。 (另請參閱https://www.macieira.org/blog/2012/01/sorry-state-of-dynamic-libraries-on-linux/和 Ulrich Drepper 的如何編寫共享庫

__attribute__ ((visibility("hidden")))
 int (*read_original)(int fd, void *data, unsigned long size) = 0;

然后gcc -save-temps tracer2.S target2.c -shared -fPIC編譯/匯編 + 鏈接共享庫。 GCC也有類似的選項-fvisibility=hidden ,使默認,要求在符號明確的屬性,想出口的動態鏈接。 如果您在庫中使用了任何全局變量,那么這是一個非常好的主意,可以讓編譯器發出使用它們的高效代碼。 它還可以保護您免受與其他庫的全局名稱沖突。 GCC 手冊強烈推薦它。

它也適用於g++ C++ 名稱修改僅適用於函數名稱,不適用於變量(包括函數指針)。 但通常不要使用 C++ 編譯器編譯.c文件。


如果您確實想支持符號插入,則需要使用 GOT; 顯然你可以看看編譯器是如何做到的

int glob;                 // with default visibility = default
int foo() { return glob; }

使用 GCC -O3 -fPIC編譯為這個 asm (沒有任何可見性選項,因此全局符號是完全全局可見的:從共享對象導出並參與符號插入)。

foo:
        movq    glob@GOTPCREL(%rip), %rax
        movl    (%rax), %eax
        ret

顯然,這比mov glob(%rip), %eax效率低mov glob(%rip), %eax因此更喜歡將全局變量的范圍限制在庫中(隱藏),而不是真正的全局變量。

您可以使用弱別名執行一些技巧,讓您導出僅該庫定義的符號,並通過“隱藏”別名有效地訪問該定義。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM