无法在 64 位 Linux 上从汇编 (yasm) 代码调用 C 标准库函数

2024-05-24

我有一个函数foo以汇编语言编写,并在 Linux (Ubuntu) 64 位上使用 yasm 和 GCC 编译。它只是使用以下命令将消息打印到标准输出puts(),如下所示:

bits 64

extern puts
global foo

section .data

message:
  db 'foo() called', 0

section .text

foo:
  push rbp
  mov rbp, rsp
  lea rdi, [rel message]
  call puts
  pop rbp
  ret

它被用GCC编译的C程序调用:

extern void foo();

int main() {
    foo();
    return 0;
}

构建命令:

yasm -f elf64 foo_64_unix.asm
gcc -c foo_main.c -o foo_main.o
gcc foo_64_unix.o foo_main.o -o foo
./foo

问题是这样的:

运行程序时,它会打印一条错误消息,并在调用期间立即出现段错误puts:

./foo: Symbol `puts' causes overflow in R_X86_64_PC32 relocation
Segmentation fault

使用 objdump 反汇编后,我发现调用是使用错误的地址进行的:

0000000000000660 <foo>:
 660:   90                      nop
 661:   55                      push   %rbp
 662:   48 89 e5                mov    %rsp,%rbp
 665:   48 8d 3d a4 09 20 00    lea    0x2009a4(%rip),%rdi
 66c:   e8 00 00 00 00          callq  671 <foo+0x11>      <-- here
 671:   5d                      pop    %rbp
 672:   c3                      retq

(671是下一条指令的地址,而不是puts)

但是,如果我用 C 重写相同的代码,则调用的方式会有所不同:

645:   e8 c6 fe ff ff          callq  510 <puts@plt>

即它引用puts来自 PLT。

是否可以告诉 yasm 生成类似的代码?


TL:DR: 3 个选项:

  • 构建一个非 PIE 可执行文件(gcc -no-pie -fno-pie call-lib.c libcall.o),因此当您编写时,链接器将透明地为您生成一个 PLT 条目call puts.
  • call puts wrt ..plt like gcc -fPIE会做。
  • call [rel puts wrt ..got] like gcc -fno-plt会做。

后两者将在 PIE 可执行文件或共享库中工作。第三种方式,wrt ..got,效率稍高一些。


默认情况下,您的 gcc 正在构建 PIE 可执行文件(x86-64 Linux 中不再允许使用 32 位绝对地址? https://stackoverflow.com/questions/43367427/32-bit-absolute-addresses-no-longer-allowed-in-x86-64-linux).

我不知道为什么,但是这样做时链接器不会自动解析call puts to call puts@plt。还有一个puts已生成 PLT 条目,但call不去那里。

在运行时,动态链接器尝试解析puts直接到该名称的 libc 符号并修复call rel32。但该符号距离超过+-2^31,因此我们收到有关溢出的警告R_X86_64_PC32搬迁。目标地址的低 32 位正确,但高位不正确。 (因此你的call跳转到错误地址)。


如果我用以下代码构建,你的代码对我有用gcc -no-pie -fno-pie call-lib.c libcall.o. The -no-pie是关键部分:它是链接器选项。您的 YASM 命令无需更改。

当创建传统的位置相关可执行文件时,链接器将puts调用目标的符号puts@plt对你来说,因为我们链接的是动态可执行文件(而不是静态链接 libc )gcc -static -fno-pie,在这种情况下call可以去directly到 libc 函数。)

无论如何,这就是 gcc 发出的原因call puts@plt(GAS 语法)编译时-fpie(桌面上的默认值,但不是https://godbolt.org/ https://godbolt.org/), 只是call puts编译时使用-fno-pie.


See @plt在这里是什么意思? https://stackoverflow.com/questions/5469274/what-does-plt-mean-here有关 PLT 的更多信息,以及Linux 上动态库的抱歉状态 https://www.macieira.org/blog/2012/01/sorry-state-of-dynamic-libraries-on-linux/从几年前开始。 (现代的gcc -fno-plt就像那篇博客文章中的想法之一。)


顺便说一句,更准确/具体的原型可以让 gcc 在调用之前避免将 EAX 归零foo:

extern void foo();在C中的意思是extern void foo(...);
你可以将其声明为extern void foo(void);,这就是()意思是C++中的。 C++ 不允许函数声明未指定参数。


装配体改进

你也可以把message in section .rodata(只读数据,作为文本段的一部分链接)。

您不需要堆栈帧,只需在调用之前将堆栈对齐 16 即可。一个假人push rax会做的。

或者我们可以尾调用puts by jumping到它而不是调用它,堆栈位置与进入该函数时相同。不管有没有 PIE,这都可以工作。只需更换call with jmp,只要 RSP 指向您自己的返回地址。

如果你想制作 PIE 可执行文件(或共享库),你有两个选择

  • call puts wrt ..plt- 通过PLT显式调用。
  • call [rel puts wrt ..got]- 通过 GOT 条目显式地进行间接调用,就像 gcc 的那样-fno-plt代码生成的风格。 (使用 RIP 相对寻址模式到达 GOT,因此rel关键词)。

WRT = 相对于。 NASM 手册文件wrt ..plt https://nasm.us/doc/nasmdoc9.html#section-9.2.5,另请参阅第 7.9.3 节:特殊符号和 WRT https://nasm.us/doc/nasmdoc7.html#section-7.9.3.

通常你会使用default rel在你的文件的顶部,这样你就可以实际使用call [puts wrt ..got]并且仍然获得 RIP 相对寻址模式。不能在 PIE 或 PIC 代码中使用 32 位绝对寻址模式。

call [puts wrt ..got]使用动态链接存储在 GOT 中的函数指针汇编为内存间接调用。 (早期绑定,而不是惰性动态链接。)

NASM文件..got用于获取变量的地址第 9.2.3 节 https://nasm.us/doc/nasmdoc9.html#section-9.2.3。 (其他)库中的函数是相同的:您从 GOT 获取指针而不是直接调用,因为偏移量不是链接时间常量并且可能不适合 32 位。

YASM 也接受call [puts wrt ..GOTPCREL],类似于 AT&T 语法call *puts@GOTPCREL(%rip),但 NASM 没有。

; don't use BITS 64.  You *want* an error if you try to assemble this into a 32-bit .o

default rel          ; RIP-relative addressing instead of 32-bit absolute by default; makes the [rel ...] optional

section .rodata            ; .rodata is best for constants, not .data
message:
  db 'foo() called', 0

section .text

global foo
foo:
    sub    rsp, 8                ; align the stack by 16

    ; PIE with PLT
    lea    rdi, [rel message]      ; needed for PIE
    call   puts WRT ..plt          ; tailcall puts
;or
    ; PIE with -fno-plt style code, skips the PLT indirection
    lea   rdi, [rel message]
    call  [rel  puts wrt ..got]
;or
    ; non-PIE
    mov    edi, message           ; more efficient, but only works in non-PIE / non-PIC
    call   puts                   ; linker will rewrite it into call puts@plt

    add   rsp,8                   ; restore the stack, undoing the add
    ret

处于一个位置——依赖的Linux可执行文件,您可以使用mov edi, message而不是 RIP 相关的 LEA。它的代码大小更小,并且可以在大多数 CPU 上的更多执行端口上运行。 (有趣的事实:MacOS 总是将“图像库”放在低 4GiB 之外,因此不可能进行这种优化。)

在非 PIE 可执行文件中,您也可以使用call puts or jmp puts并让链接器对其进行排序,除非您想要更高效的 no-plt 风格动态链接。但如果您确实选择静态链接 libc,我认为这是直接跳转到 libc 函数的唯一方法。

(我认为非 PIE 静态链接的可能性是why ld愿意为非 PIE 自动生成 PLT 存根,但不为 PIE 或共享库自动生成 PLT 存根。它要求您说出链接 ELF 共享对象时的含义。)

如果你确实使用过call puts在 PIE 中(call rel32),只有当您静态链接位置无关的实现时它才可以工作puts到你的 PIE 中,所以整个事情是一个可执行文件,它将在运行时加载到随机地址(通过通常的动态链接器机制),但根本不依赖于libc.so.6


当目标在静态链接时出现时,链接器“放松”调用

GAS call *bar@GOTPCREL(%rip) uses R_X86_64_GOTPCRELX(放松)
NASM call [rel bar wrt ..got] uses R_X86_64_GOTPCREL(不放松)

对于手写汇编来说,这不是一个问题;你可以使用call bar当您知道该符号将出现在另一个符号中时.o(而不是.so)您要链接的内容。但是 C 编译器不知道库函数和您用原型声明的其他用户函数之间的区别(除非您使用类似的东西)gcc -fvisibility=hidden https://gcc.gnu.org/wiki/Visibility https://gcc.gnu.org/wiki/Visibility或属性/编译指示)。

不过,如果您静态链接库,您可能希望编写链接器可以优化的 asm 源代码,但据我所知,您不能使用 NASM 做到这一点。您可以将符号导出为隐藏(在静态链接时可见,但在最终 .so 中动态链接时不可见):global bar:function hidden,但那是在定义函数的源文件中,而不是访问它的文件中。


global bar
bar:
    mov eax,231
    syscall
    call bar wrt ..plt
    call [rel bar wrt ..got]
extern bar

组装后的第二个文件nasm -felf64并拆卸objdump -drwc -Mintel查看搬迁:

0000000000000000 <.text>:
   0:   e8 00 00 00 00          call   0x5      1: R_X86_64_PLT32       bar-0x4
   5:   ff 15 00 00 00 00       call   QWORD PTR [rip+0x0]        # 0xb 7: R_X86_64_GOTPCREL    bar-0x4

链接后ld(GNU Binutils)2.35.1 -ld bar.o bar2.o -o bar

0000000000401000 <_start>:
  401000:       e8 0b 00 00 00          call   401010 <bar>
  401005:       ff 15 ed 1f 00 00       call   QWORD PTR [rip+0x1fed]        # 402ff8 <.got>
  40100b:       0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]

0000000000401010 <bar>:
  401010:       b8 e7 00 00 00          mov    eax,0xe7
  401015:       0f 05                   syscall 

请注意,PLT 形式已放宽为直接形式call bar,PLT 被消除。但是ff 15调用 [rel mem] 是not放松到e8 rel32

使用气体:

_start:
        call    bar@plt
        call    *bar@GOTPCREL(%rip)

gcc -c foo.s && disas foo.o

0000000000000000 <_start>:
   0:   e8 00 00 00 00          call   5 <_start+0x5>   1: R_X86_64_PLT32       bar-0x4
   5:   ff 15 00 00 00 00       call   QWORD PTR [rip+0x0]        # b <_start+0xb>      7: R_X86_64_GOTPCRELX   bar-0x4

请注意 R_X86_64_GOTPCRELX 末尾的 X。
ld bar2.o foo.o -o bar && disas bar:

0000000000401000 <bar>:
  401000:       b8 e7 00 00 00          mov    eax,0xe7
  401005:       0f 05                   syscall 

0000000000401007 <_start>:
  401007:       e8 f4 ff ff ff          call   401000 <bar>
  40100c:       67 e8 ee ff ff ff       addr32 call 401000 <bar>

两个电话都放松为直接e8 call rel32直接到达目标地址。间接调用中的额外字节填充为67地址大小前缀(对call rel32),将指令填充到相同的长度。 (因为现在重新组装和重新计算函数内的所有相关分支以及对齐等已经太晚了。)

这会发生在call *puts@GOTPCREL(%rip)如果你静态链接 libc,gcc -static.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

无法在 64 位 Linux 上从汇编 (yasm) 代码调用 C 标准库函数 的相关文章

  • 如何获取 (Linux) 机器的 IP 地址?

    这个问题和之前问的几乎一样如何获取本地计算机的IP地址 https stackoverflow com questions 122208 get the ip address of local computer 问题 但是我需要找到一个的I
  • 并行运行 shell 脚本

    我有一个 shell 脚本 打乱大型文本文件 600 万行和 6 列 根据第一列对文件进行排序 输出 1000 个文件 所以伪代码看起来像这样 file1 sh bin bash for i in seq 1 1000 do Generat
  • 如何让R使用所有处理器?

    我有一台运行 Windows XP 的四核笔记本电脑 但查看任务管理器 R 似乎一次只使用一个处理器 如何让 R 使用全部四个处理器并加速我的 R 程序 我有一个基本系统 我使用它在 for 循环上并行化我的程序 一旦您了解需要做什么 此方
  • 在 Mono 上运行 .Net MVC5 应用程序

    我正在 Windows 上的 Visual Studio 2013 中开发 Net 4 5 1 MVC5 应用程序 现在我想知道 是否可以在Linux Ubuntu 12 04 上运行这个应用程序 可以使用OWIN吗 Owin 可以自托管运
  • 如何使用waf构建共享库?

    我想使用构建一个共享库waf http code google com p waf 因为它看起来比 GNU 自动工具更容易 更简洁 到目前为止 我实际上有几个与我开始编写的 wscript 有关的问题 VERSION 0 0 1 APPNA
  • 无法理解寄存器和变量之间的汇编mov指令

    我在 64 位 Linux 上使用 NASM 汇编器 有一些关于变量和寄存器的东西我无法理解 我创建一个名为 msg 的变量 msg db hello world 现在 当我想写入标准输出时 我移动msg to rsi注册 但我不明白mov
  • 如何在 Linux 中使用 C 语言使用共享内存

    我的一个项目有点问题 我一直在试图找到一个有据可查的使用共享内存的例子fork 但没有成功 基本上情况是 当用户启动程序时 我需要在共享内存中存储两个值 当前路径这是一个char and a 文件名这也是char 根据命令参数 启动一个新进
  • 在生产服务器上使用 Subversion 使文件生效的最佳方法是什么?

    目前我已经设置了 subversion 这样当我在 Eclipse PDT 中进行更改时 我可以提交更改 它们将保存在 home administrator 中项目文件 该文件具有 subversion 推荐的 branches tags
  • 如何在 *nix 中登录时运行脚本?

    我知道我曾经知道如何做到这一点 但是 如何在 unix 中登录时运行脚本 bash 可以 From 维基百科 Bash http en wikipedia org wiki Bash 28Unix shell 29 当 Bash 启动时 它
  • linux下如何从文本文件中获取值

    我有一些文本格式的文件 xxx conf 我在这个文件中有一些文本 disablelog 1 当我使用 grep r disablelog oscam conf 输出是 disablelog 1 但我只需要值1 请问你有什么想法吗 一种方法
  • 在 OllyDbg 和 Assembler 中,EBP+8 是什么意思?

    我正在学习 OllyDbg 中的汇编和调试技巧 以便学习如何使用未记录的函数 现在我遇到以下问题 我有以下代码部分 来自 OllyDbg MOV EDI EDI PUSH EBP MOV EBP ESP MOV EAX DWORD PTR
  • cdc_acm:无法设置 dtr/rts - 无法与 USB cdc 设备通信

    我试图使用 pic24fj128gb206 枚举 usb cdc 设备 设备似乎已正确枚举 但是当我将设备连接到 Linux PC 时 我从内核收到以下警告消息 cdc acm 1 8 1 6 7 1 0 failed to set dtr
  • 使用8086汇编语言画圆[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我试图使用 8086 汇编器画一个圆 我尝试利用中点圆算法 https en wikipedia org wiki Midpoin
  • 使用自定义堆的类似 malloc 的函数

    如果我希望使用自定义预分配堆构造类似 malloc 的功能 那么 C 中最好的方法是什么 我的具体问题是 我有一个可映射 类似内存 的设备 已将其放入我的地址空间中 但我需要获得一种更灵活的方式来使用该内存来存储将随着时间的推移分配和释放的
  • 无法显示 Laravel 欢迎页面

    我的服务器位于 DigitalOcean 云上 我正在使用 Ubuntu 和 Apache Web 服务器 我的家用计算机运行的是 Windows 7 我使用 putty 作为终端 遵循所有指示https laracasts com ser
  • 如何wget目录中最新的文件

    我想编写一个 bash 脚本来下载并安装最新的每日构建程序 RStudio 是否有可能使wget仅下载目录中最新的文件http www rstudio org download daily desktop http www rstudio
  • 具有多处理功能的 Python 代码无法在 Windows 上运行

    以下简单的绝对初学者代码在 Ubuntu 14 04 Python 2 7 6 和 Cygwin Python 2 7 8 上运行 100 但在 Windows 64 位 Python 2 7 8 上挂起 我使用另一个片段观察到了同样的情况
  • 如何在shell脚本中给出密码?

    在 shell 脚本文件中 我使用一些命令 例如scp and make install要求我输入密码 我运行一个 shell 脚本来编译一个大项目 一段时间后它会要求我输入密码才能使用scp 我需要等待该过程并在此之后提供密码 我只想通过
  • 如何在Linux中自动启动需要X的应用程序

    我试图在系统进入运行级别 5 时自动启动 X 应用程序 这样做的正确方法是什么 我写了一个脚本并将其放在 etc init d 中 我已运行适当的 chkconfig 命令来设置 etc rcX d 目录中的符号链接 一切工作正常 除了当我
  • 如何找到进程启动时使用的原始用户名?

    有一个 perl 脚本需要以 root 身份运行 但我们必须确保运行该脚本的用户最初没有以用户 foo 身份登录 因为它将在脚本运行期间被删除 那么 我如何查明自登录以来可能已多次起诉的用户是否在该链中的任何时间都没有模拟过 foo 我发现

随机推荐

  • Android Studio 无法解析存储库

    在我的项目中 我尝试使用设计支持库 我的 Gradle 文件中有 dependencies compile com android support design 当我尝试构建这个时 我收到错误 通常我会点击Install Repositor
  • 使用 CSS 等高列

    我想对我的 CSS 表使用百分比 不幸的是 它对我不起作用 这段代码有什么问题 我应该使用 flexbox 而不是 table 吗 我想使用表格 因为我想要相同高度的列 ul list style none margin 0 display
  • sed 中的正则表达式用于在一条语句中进行多个替换

    我想清理一些输入并用可接受的输入替换几个字符 例如一个丹麦人 with aa 使用多个语句可以轻松完成此操作 例如 ae aa oe 但由于工具限制 我希望能够在单个正则表达式中完成此操作 我可以捕获所有相关案例 但我的替换不能按我想要的方
  • MongoDB:尝试从 JSON 读取 Long 导致 java.lang.Integer 无法转换为 java.lang.Long

    我有一个代码可以从 MongoDB 读取特定格式的数据 我需要测试一下 为此 我使用要测试的数据创建一个 JSON id ObjectId 57552e32e4b0839ede67e0af serial 574000690 startDat
  • 如何在特定时间以毫秒精度触发 C# 函数?

    我有两台计算机 它们的时间通过 NTP 同步 确保时间仅相差几毫秒 其中一台计算机将通过 TCP 向另一台计算机发送一条消息 以在两台计算机上的未来指定时间启动某个 c 函数 我的问题是 如何在特定时间以毫秒精度 或更好 触发 C 中的函数
  • 将node_modules安装到vendor

    如何在本地为每个项目安装 npm 模块vendor node modules和做package json文件看到他们 我不想将 package json 移动到供应商文件夹 我有凉亭 在 bowerrc我指定bower components
  • OpenIdConnect.nonce cookie 过多导致错误页面“错误请求 - 请求太长”

    我正在使用 OWIN OAuth 和 OpenId Connect 身份验证 Microsoft Owin Security OpenIdConnect 在 C ASP MVC Web 应用程序中 使用 Microsoft 帐户的 SSO
  • JSON 对象数组转 Java POJO

    将此 JSON 对象转换为 java 中的类 您的 POJO 类中的映射将如何 ownerName Robert pets name Kitty name Rex name Jake This kind of question is ver
  • flexslider 中的 GIF 滑块,如何仅在滑块上时开始 gif

    现在我有一个带有四个幻灯片的 Flexslider 第三个滑块是 gif 而不是像其他滑块一样是 jpg 我遇到的问题是 第三个 gif 滑块显然在到达页面时立即启动 而不是在您实际到达该滑块时启动 当点击前两个滑块时 gif 就快完成了
  • Karma + JSPM + Typescript - 未找到“.ts.js”

    主要只是想让 Karma JSPM 在加载 ts 文件时发挥良好作用 但绝对没有运气 我看到一个讨论库 https github com Larchy karma jspm typescript coverage tree master一个
  • 异步 JS 加载到 head 中

    我需要将脚本异步加载到页面上 我正在使用createElement方法在头部动态插入脚本标签 发生的事情是首先加载页面源 完成后 头部中包含的所有元素都会并行加载 一旦全部加载完毕 我动态添加的脚本就会加载 这在逻辑上是有道理的 但我正在寻
  • 监听外部事件。 Bash 到 NodeJS 的桥梁

    在 NodeJS 进程内部 我如何监听来自 bash 的事件 例如 NodeJS side obj on something function data console log data Bash side do something Hel
  • Spring 术语中命令、表单、业务和实体对象之间的区别?

    我试图理解这些对象在松散耦合系统方面的差异 业务对象与实体对象相同吗 我可以使用 MVC 中的业务或实体对象作为我的命令对象吗 命令对象与表单对象相同吗 只是寻找 Spring 术语和用法中对象类型的说明 我在 stackoverflow
  • 如何让 LibGDX gradled eclipse 项目在 AIDE(Android IDE 应用程序)上工作

    我正在尝试让 libgdx gradle 项目在 AIDE 上运行 AIDE 是 Android 上的一款应用程序 其作用类似于 IDE 它似乎不起作用 因为 buildconfig java 没有生成包名称 有人有任何解决方法吗 好的 我
  • 使用 Json.NET 序列化子类

    我正在尝试使用 Json NET 序列化子类 生成的 json 包含超类的序列化属性 但是not子类对象的属性 这似乎与我发现的一个问题有关这里就这样 https stackoverflow com q 5863496 498969 但必须
  • IdentityServer4-挑战所有对 API 的请求,而不仅仅是 [授权]

    我有一个使用 IdentityServer4 的 ASP Net Core 2 API 我想质询对服务器的所有请求 并在用户未经过身份验证时调用登录重定向 在身份验证后回调到特定的 URL 默认情况下 仅当未经身份验证的用户请求受 Auth
  • 如何将一串Python代码编译成一个可以调用函数的模块?

    在 Python 中 我有一串 Python 源代码 其中包含以下函数 mySrc def foo print foo def bar print bar 我想将这个字符串编译成某种形式类似模块的对象这样我就可以调用代码中包含的函数 这是我
  • JQuery:提交时不起作用

    我想要捕获所有表单提交事件 从操作属性获取 url 并使用它通过 AJAX 将表单内容发送到该地址 所以我只需要一个提交事件处理程序 然而我很快就遇到了麻烦 因为它似乎无法在 IE 中工作 document submit function
  • 通过spark-shell以静默模式执行scala脚本

    需要通过spark shell以静默模式执行scala脚本 当我使用时spark shell i file scala 执行后 我进入scala交互模式 我不想进入那里 我尝试执行spark shell i file scala 但我不知道
  • 无法在 64 位 Linux 上从汇编 (yasm) 代码调用 C 标准库函数

    我有一个函数foo以汇编语言编写 并在 Linux Ubuntu 64 位上使用 yasm 和 GCC 编译 它只是使用以下命令将消息打印到标准输出puts 如下所示 bits 64 extern puts global foo secti