【问题标题】:Simplest worked example of printf in NASM failsNASM 中最简单的 printf 工作示例失败
【发布时间】:2021-03-16 17:29:10
【问题描述】:

使用此代码:

section .data
    msg db "Most basic printf example in NASM", 0xA, 0xD, 0
    len equ  $-msg

section .bss

section .text
    global _start       
    extern printf

_start:                 
    mov  rsi, msg
    push rax ;; for stack alignment 
    call printf

失败并出现错误undefined reference to `printf'

我正在尝试创建从 NASM 调用 printf 的最简单的工作示例。

我已经阅读了将 -lc 添加到 ld 的建议,即:

ld -o worked-example -lc worked-example.o 要么 ld -o worked-example worked-example.o -lc 两者都可以防止错误消息 - 但是文件工作示例 - 无法使用 ./worked-example - even 虽然可以看到 catls

file worked-example 表示: 工作示例:ELF 64 位 LSB 可执行文件,x86-64,版本 1 (SYSV),动态链接,解释器 /lib/ld64.so.1,带有 debug_info,未剥离

编辑:基于下面的 cmets,我找到了动态链接器并尝试添加:ld worked-example.o -o worked-example -lc -dynamic-linker /usr/lib64/ld-linux-x86-64.so.2 哪个有效。 [带有段错误并且没有打印任何内容 - 但它可以编译并运行]

【问题讨论】:

  • 名称修改适用于 C++,会导致链接器错误。
  • 运行时会出现错误,就像找不到文件本身一样,因为您没有指定在加载时运行的动态链接器。在 64 位 Linux 发行版上,通常不能像 ld -o worked-example -lc worked-example.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 那样完成。您还必须与 -lc 链接。如果您使用 GCC 链接,您可以避免这种情况,因为它会为您执行此操作并在 C 库中链接
  • 我的第一条评论应该是 在通常无法做到的 64 位 Linux 发行版上。需要注意的是,通过绕过 C 启动代码从 C 库运行代码可能会导致某些类型的 C 函数调用出现问题。如果您使用 MUSL C 库,则可以避免该问题,因为它可以以不需要事先初始化的方式使用。
  • 至于它运行不正常是因为第一个参数在 RDI 中,但你把它放在了 RSI 中。由于printf 是一个可变参数函数,您应该指定用于在寄存器 AL 中进行调用的向量寄存器(SIMD 寄存器)的最大数量。在这种情况下,您没有使用向量寄存器,因此您可以将 AL 设置为 0。您的代码也缺少退出程序的内容。 call exit
  • 格式字符串确实进入 RDI,因为它是第一个参数。您的格式字符串是"Most basic printf example in NASM", 0xA, 0xD, 0。它是一个格式字符串,其中的字符串不包含任何 C 格式说明符。

标签: linux assembly x86-64 nasm glibc


【解决方案1】:

迈克尔帮助填补了缺失的部分。 最大的问题是链接器命令。 编译:
nasm -f elf64 -F dwarf -g worked-example.asm

那么链接器需要调用dynamic linker:
ld worked-example.o -o worked-example -lc -dynamic-linker /usr/lib64/ld-linux-x86-64.so.2

具体路径因发行版而异。
使用 `sudo find / -iname ld-linux-x86-64.so.2" 为您的系统找到正确的路径。

在关注链接问题时,我忘了干净地结束程序:

mov eax, 1 ; sys exit
mov ebx, 0 ; all OK 
int 80h    ; call the kernel

也许,最容易理解的工作示例是:

section .data
    msg db "Most basic printf example in NASM", 0xA, 0
    len equ  $-msg

section .bss

section .text
    global _start       
    extern printf
   
_start:                 
    mov rdi, msg
    call printf
    
   ;end gracefully 
   mov eax, 1 
   mov ebx, 0
   int 80h 

但是,这样做存在一些问题。尊重彼得, 这是一种更精致、基本相同的方法:

section .data
    msg:  db "Most basic printf example in NASM", 0xA, 0
    len equ  $-msg

section .text
    global _start       
    extern printf
    extern exit
   
_start:                 
    xor  eax, eax          ; 0 FP args
    lea  rdi, [rel msg]
    call printf

    xor  edi, edi
    call exit              ; exit(0)
     ; let C handle any stdio flushing before exiting
     ; see here: https://stackoverflow.com/questions/38379553/using-printf-in-assembly-leads-to-an-empty-ouput

【讨论】:

  • 1.不建议使用 64 位代码中的 int 0x80(尽管退出基本上没问题)。 What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?。 2. call exit 在 printf 之后,确保即使 stdout 是全缓冲的,stdio 缓冲区也会被刷新,例如从将输出重定向到文件。通常不要将 stdio 输出与原始退出系统调用混合。 Using printf in assembly leads to an empty ouput
  • mov rdi, msg 是将标签/符号地址放入寄存器的最糟糕方式。随处可用的标准方法是相对于 RIP 的 LEA,如果您想针对 Linux 非 PIE 进行优化,则为 mov edi, msgHow to load address of function or label into register in GNU Assembler(我的回答包括 NASM 示例)。 mov rdi, msg 确实可以工作(甚至可以在 PIE 可执行文件中工作,这要归功于运行时修复),它大多只是效率低下。
  • 应该将 AL 归零以告诉 printf XMM 寄存器中有 0 个 FP 参数。在call 之前使用xor eax,eax。在当前的 GNU/Linux 系统中,gcc 编译可变参数函数以检查 AL!=0 并转储所有 8 个 XMM0..7 寄存器或不转储(这就是您的代码碰巧工作的原因),但早期版本使用 AL 进行计算跳转和会在 AL>8 时崩溃。 (ABI 不保证在进入 _start 时将寄存器归零。这发生在静态可执行文件中,因为内核这样做是为了避免泄漏可能的内核数据,但动态链接器在 _start 之前在您的进程中运行。)
  • 0xA, 0xD - 我们在 GNU/Linux 中不使用 DOS 行尾,只使用 0xa = \n 换行符。我在您的第二个版本中调整了一些其他内容(例如样式:在标签后使用 :,即使在数据部分也是如此),但我将把该行的结尾留给您。
猜你喜欢
  • 2019-03-07
  • 1970-01-01
  • 2013-11-14
  • 2014-07-12
  • 1970-01-01
  • 2014-11-20
  • 1970-01-01
  • 1970-01-01
  • 2016-10-08
相关资源
最近更新 更多