如何在 x86-64 Linux 上的 GAS 程序集中通过 GOT 访问 C 全局变量? [英] How to access a C global variable through GOT in GAS assembly on x86-64 Linux?

查看:76
本文介绍了如何在 x86-64 Linux 上的 GAS 程序集中通过 GOT 访问 C 全局变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一个共享库(不是可执行文件,所以请不要告诉我使用 -no-pie)与单独文件中的程序集和 C(不是内联程序集).

I am trying to write a shared library(not an executable, so please do not tell me to use -no-pie) with assembly and C in separate files(not inline assembly).

我想通过汇编代码中的全局偏移表访问一个 C 全局变量,因为调用的函数可能在任何其他共享库中定义.

And I would like to access a C global variable through Global Offset Table in assembly code, because the function called might be defined in any other shared libraries.

我知道 PLT/GOT 的东西,但我不确定如何告诉编译器为链接器正确生成重定位信息(语法是什么),以及如何告诉链接器实际重定位我的代码信息(链接器选项是什么).

I know the PLT/GOT stuff but I do not know for sure how to tell the compiler to correctly generate relocation information for the linker(what is the syntax), and how to tell the linker to actually relocate my code with that information(what is the linker options).

我的代码编译时出现链接错误

My code compiles with a linking error

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

此外,如果有人能分享一些关于重新定位的 GAS 组件的详细文档会更好.例如,关于如何使用 GNU 汇编器在 C 和汇编之间进行插值的详尽列表.

Furthermore, it would be better if someone could share some detailed documentation on the GAS assembly about relocation. For example, an exhaustive list on how to interpolate between C and assembly with GNU assembler.

编译 C 和汇编代码并将其链接到一个共享库中.

Compile the C and assembly code and link the into ONE shared library.

# 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

期望与结果

我希望链接器愉快地链接我的目标文件,但它说

Expectation and Result

I expect the linker to happily link my object files but it says

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 表条目的偏移量.

The keyword GOTPCREL will tell the compiler this is a PC-relative relocation to GOT table. The linker will calculate the offset from current rip to the target GOT table entry.

您可以通过

$ 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

感谢彼得.

  call read@plt

objdump 显示它调用了正确的 PLT 条目.

objdump shows it calls into the correct PLT entry.

$ 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

2. I can lea a PLT entry address correctly

0xffffff23 is -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.
    

    推荐答案

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

    Apparently the linker enforces global vs. hidden visibility for symbols in ELF shared objects, not allowing "back door" access to symbols that participate in symbol-interposition (and thus can potentially be more than 2GB away.)

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

    To access it directly from other code in the same shared object with normal RIP-relative addressing, make the symbol hidden by setting its ELF visibility as such. (See also https://www.macieira.org/blog/2012/01/sorry-state-of-dynamic-libraries-on-linux/ and Ulrich Drepper's 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 手册强烈推荐它.

    Then gcc -save-temps tracer2.S target2.c -shared -fPIC compiles/assembles + links a shared library. GCC also has options like -fvisibility=hidden that makes that the default, requiring explicit attributes on symbols you do want to export for dynamic linking. That's a very good idea if you have any globals that you use inside your library, to get the compiler to emit efficient code for using them. It also protects you from global name-clashes with other libraries. The GCC manuals strongly recommends it.

    它也适用于 g++;C++ 名称修改仅适用于函数名称,不适用于变量(包括函数指针).但一般不要用 C++ 编译器编译 .c 文件.

    It also works with g++; C++ name mangling only applies to function names, not variables (including function-pointers). But generally don't compile .c files with a C++ compiler.

    如果确实要支持符号插入,则需要使用 GOT;显然你可以看看编译器是怎么做的:

    If you do want to support symbol interposition, you need to use the GOT; obviously you can just look at how the compiler does it:

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

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

    compiles to this asm with GCC -O3 -fPIC (without any visibility options, so global symbols are fully globally visible: exported from shared objects and participating in symbol interposition).

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

    显然这比 mov glob(%rip), %eax 效率低,所以更喜欢将全局变量的范围限制在库中(隐藏),而不是真正的全局变量.

    Obviously this is less efficient than mov glob(%rip), %eax so prefer keeping your global vars scoped to the library (hidden), not truly global.

    您可以使用弱别名执行一些技巧,让您导出仅由该库定义的符号,并通过隐藏"别名高效访问该定义.

    There are tricks you can do with weak aliases to let you export a symbol that only this library defines, and access that definition efficiently via a "hidden" alias.

    这篇关于如何在 x86-64 Linux 上的 GAS 程序集中通过 GOT 访问 C 全局变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆