【问题标题】:How to access a C global variable through GOT in GAS assembly on x86-64 Linux?如何在 x86-64 Linux 上的 GAS 程序集中通过 GOT 访问 C 全局变量?
【发布时间】:2019-08-09 03:54:47
【问题描述】:

我的问题

我正在尝试编写一个共享库(不是可执行文件,所以请不要告诉我使用-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 和汇编代码并将其链接到 ONE 共享库中。

# 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 会告诉编译器这是一个PC 相对重定位到GOT 表。链接器将计算从当前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函数
  call read@plt

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

0000000000001020 <.plt>:
    1020:       ff 35 e2 2f 00 00       pushq  0x2fe2(%rip)        # 4008 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:       ff 25 e4 2f 00 00       jmpq   *0x2fe4(%rip)        # 4010 <_GLOBAL_OFFSET_TABLE_+0x10>
    102c:       0f 1f 40 00             nopl   0x0(%rax)

0000000000001030 <read@plt>:
    1030:       ff 25 e2 2f 00 00       jmpq   *0x2fe2(%rip)        # 4018 <read@GLIBC_2.2.5>
    1036:       68 00 00 00 00          pushq  $0x0
    103b:       e9 e0 ff ff ff          jmpq   1020 <.plt>

0000000000001109 <read1>:
    1109:       48 8d 04 25 23 ff ff    lea    0xffffffffffffff23,%rax
    1110:       ff
    1111:       ff e0                   jmpq   *%rax

环境

  • 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.

【问题讨论】:

  • .global read_original 没有定义符号,它只标记它以导出 from 这个对象,如果 / 当它用 read_original: 标签定义时。共享库必须通过 GOT 才能访问主可执行文件或另一个共享库中的静态数据,因为它们可以加载超过 2GB,并且偏移量不是编译时常量。 (查看 C 编译器输出以访问 extern int read_original。)此外,您的 LEA/JMP 等效于 jmp read_original,但更糟。您的意思是 mov / jmp 加载函数指针吗?
  • 这看起来很像另一个启发性@PeterCordes回答:)的开始
  • 您真的需要将您的 asm 与定义全局的 C 语言分开放在一个共享库中吗?通常你只需将.c.s 链接到一个共享库中。在 asm 中,您可以使用正常的 RIP 相对寻址访问您自己的共享对象中的全局变量。
  • 嗨@PeterCordes,1。我不认为.global read_original 定义了符号以太。符号在target2.c中定义; 2. 我知道我们需要通过 GOT,我在问具体怎么做; 3.lea/jmp只是问题的演示,所以请不要介意时间成本:)
  • @PeterCordes 我的 asm 和 C 在同一个共享库 liba.so 中,不知道您是否正确理解我的问题。我将它们链接到一个共享库中。而 RIP 相对寻址正是我想要做的。如果我没有说清楚,请告诉我。

标签: linux shared-libraries x86-64 gnu-assembler got


【解决方案1】:

显然,链接器对 ELF 共享对象中的符号强制执行全局与隐藏可见性,不允许“后门”访问参与符号插入的符号(因此可能超过 2GB。)

要使用正常的 RIP 相对寻址从同一共享对象中的其他代码直接访问它,请通过设置其 ELF 可见性来使符号隐藏。 (另请参阅 https://www.macieira.org/blog/2012/01/sorry-state-of-dynamic-libraries-on-linux/ 和 Ulrich Drepper 的 How to Write Shared Libraries

__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; }

compiles to this asm with GCC -O3 -fPIC(没有任何可见性选项,因此全局符号完全全局可见:从共享对象导出并参与符号插入)。

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

显然这比mov glob(%rip), %eax 效率低,因此更喜欢将全局变量保持在库(隐藏)的范围内,而不是真正的全局变量。

您可以使用弱别名执行一些技巧,让您导出只有此库定义的符号,并通过“隐藏”别名有效地访问该定义。

【讨论】:

  • 谢谢彼得,GOTPCREL 正是我要问的!我想知道在哪里可以找到这些glob@blabla 魔法的详尽列表。也感谢你的博文。但我认为在 C/C++ 中,自己试验并对结果做出某些假设会招致未定义的行为。所以我不认为模仿编译输出是一个好主意。它不是文档和手册的替代品。
  • @AlexWang:这不是我的博客post,只是我到Thiago 博客的链接。但无论如何,希望语法记录在 GAS 手册 (sourceware.org/binutils/docs/as) 中。关于为什么/如何使用 GOT 的详细信息可能在 ABI 文档中?我不知道。 GOT 保存符号的地址,动态链接器在运行时将其填充,因此在这种情况下,您只需复制编译器为执行您想要的 C 函数所做的操作。只要你遵循调用约定/ABI,并且不做任何 C 编译器不会做的事情(比如写入 GOT),你就不应该得到任何 UB。
  • @AlexWang: TL:DR: 如果你对大局中的事情有足够的了解,那么查看编译器输出对于正确获取语法细节非常有用。
  • 是的,我当然同意你的看法。我只是想说它不应该取代文档/手册的作业。
  • g++ 将文件编译为 C++ 而不是 C,但这可以正常工作,因为 C++ 名称修改仅适用于函数 names 而不适用于变量名称,即使它们具有函数指针类型。
猜你喜欢
  • 1970-01-01
  • 2019-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-10
  • 1970-01-01
相关资源
最近更新 更多