c - 试图从汇编程序(64 位)的 glibc 调用 C 函数
问题描述
我一直在逐步学习汇编语言:第三版,并在最后一章“走向 C”。我正在尝试获得一种一致的方法来转换 32 位代码,该代码puts
在我的 64 位 Ubuntu 系统上调用 C 库 (glibc) 函数。(我想继续阅读文本的最后 50 页,这可能更深入地了解 C [更多令人讨厌的双关语],但来自用 32 位代码编写的汇编库)。代码是:
SECTION .data ; Section containing initialised data
EatMsg: db "Eat at Joe's!",0
SECTION .text ; Section containing code
extern puts ; Simple "put string" routine from clib
global main ; Required so linker can find entry point
main:
push ebp ; Set up stack frame for debugger
mov ebp,esp
push ebx ; Must preserve ebp, ebx, esi, & edi
push esi
push edi
;;; Everything before this is boilerplate; use it for all ordinary apps!
push EatMsg ; Push address of message on the stack
call puts ; Call clib function for displaying strings
add esp,4 ; Clean stack by adjusting ESP back 4 bytes
;;; Everything after this is boilerplate; use it for all ordinary apps!
pop edi ; Restore saved registers
pop esi
pop ebx
mov esp,ebp ; Destroy stack frame before returning
pop ebp
ret ; Return control to Linux
建议的 nasm 和链接器命令是
nasm -f elf -g -F stabs eatclib.asm
gcc eatclib.o -o eatclib
我找到的最接近解决方案的方法是:Call C functions from 64-bit assembly。
我尝试将扩展寄存器转换为rbp
,rsp
等;在调用之后将堆栈指针调整为 8 位而不是 4 位puts
,并使用以下命令调整生成文件:
nasm -f elf64 -g -F dwarf eatclib.asm
和
gcc eatclib.o -o eatclib -m64 -static
但出现分段错误。
我对 C 调用约定的理解仍然模糊/模糊,以至于当我尝试使用 gdb 调试器时,我并没有真正深入尝试找出错误(这些问题都只是对 32 位有点熟悉约定,而不是 C)。本书旨在为几乎没有 C 语言背景的新手汇编程序员提供介绍性书籍。
在另一个方向尝试,一个简单的 C 程序使用 puts 和一个字符串生成文件(使用 gcc-S
选项):
.file "SayHello.c"
.text
.section .rodata
.align 8
.LC0:
.string "This is based on an example from C Primer Plus"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
leaq .LC0(%rip), %rdi
call puts@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
编译的代码在这里运行(我理解其中的大部分内容,除了.cfi
指令、含义.rodata
以及为什么 gas 将其卡在 .@PLT
上puts
。)这当然是 gas 语法和我使用的文本主要是 NASM 功能。
我也尝试过使用加载器而不是 gcc,并在Professional Assembly Language (by Richard Blum)的第 89 页上找到了一行
ld -dynamic-linker /lib/ld-linux.so.2 -o eatclib -lc eatclib.o
但最终会出现我之前遇到的非常典型的链接器错误:
ld: i386 architecture of input file `eatclib.o' is incompatible with i386:x86-64 output
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400250
makefile:2: recipe for target 'eatclib' failed
我尝试将-m32
选项传递给链接器也无济于事。
无论如何,我正在寻找可行的建议。在我的搜索中,我看到人们建议使用apt-get
和安装新的(实际上是旧的)库的示例,但这些似乎有效地破坏了系统范围内的 64 位内容——当我能够运行以前的 32 位代码时,这看起来相当激烈将-melf_i386
选项传递给链接器)。
解决方案
要汇编和链接使用 libc 的 64 位 nasm 代码,请键入:
nasm -f elf64 program.asm
gcc -o program program.o
根据您的系统和编程风格,您可能需要传递-no-pie
给gcc
以便它接受与位置相关的代码。
不建议在 libc 中链接时直接调用链接器,因为没有稳定的方法可以手动拉入 C 运行时初始化代码。仅仅传递-lc
给链接器不足以让 libc 正常工作。
请注意elf64
使 nasm 发出 64 位目标文件。gcc 可以在 64 位平台上使用 64 位代码,除非另有说明,因此不需要其他选项。您可能想要添加调试符号,但请记住 stabs 是一种过时的格式。你可能想要这个:
nasm -f elf64 -gdwarf program.asm
机械地转换源代码或多或少是可能的。请记住以下差异:
- 指针和栈槽长度为 8 字节,所有通用寄存器都扩展为 8 字节;前 8 个寄存器的 64 位变体称为
rax
,rcx
,rdx
,rbx
,rsp
,rbp
,rsi
, 和rdi
. r8
存在 8 个新的通用寄存器r15
。它们的 32 位、16 位和 8 位版本称为r8d
,r8w
, r8b` 等。- SSE 指令用于浮点而不是 x87 指令
- 64 位代码通常遵循与 32 位代码不同的调用约定。在 Linux 等类 UNIX 系统上,通常使用amd64 SysV ABI 。
rdi
在此 ABI 中,标量参数在寄存器、rsi
、rdx
、rcx
、r8
和中从左到右传递r9
。寄存器rbx
,rbp
,rsp
,r12
,r13
,r14
, 和r15
必须由被调用者保存,所有其他通用寄存器可以自由覆盖。浮点参数在 SSE 寄存器中传递和返回。如果参数太多,则会在堆栈上传递额外的参数。 - SysV ABI 要求堆栈指针在函数调用时与 16 字节对齐。由于
call
指令压入 8 个字节,push rbp
而函数序言中的指令又压入 8 个字节,因此默认情况下是这种情况,除非您手动在堆栈上分配空间。请记住以 16 个字节为增量。
这是您问题中的代码转换为 64 位代码。所有更改均已标记:
SECTION .data
EatMsg: db "Eat at Joe's!",0
SECTION .text
extern puts
global main
main: ; function entry (stack alignment: 16 bytes + 8 bytes)
push rbp ; setup...
mov rbp, rsp ; the stack frame (stack now aligned to 16 bytes + 0 bytes)
; since we have so many registers, I only preserve those
; I want to use and that must be preserved, of which there
; are none in this program.
lea rdi, [rel EatMsg] ; load address of EatMsg into rdi
call puts ; call puts
; no cleanup needed as we have not pushed anything
pop rbp ; restore rbp
ret ; return
请注意,我遗漏了一堆样板文件。 lea
用于加载地址EatMsg
而不是更简单的地址,mov rdi, EatMsg
因此您的程序与位置无关。如果你不知道这意味着什么,你可以放心地忽略这个花絮,直到以后。
最后,您通常可以忽略 cfi 指令。它们为异常处理添加元数据,这仅在您的代码调用引发异常的 C++ 函数时才重要。它们不会改变代码本身的行为。
推荐阅读
- python - TypeError: “NoneType” object is not callable in Google Colab
- powerbi - 便于在其他项目(PBI、Spotfire、Tableau)中重复使用的代码片段或代码库
- aem - 在自定义 AEM 组件中以编程方式触发保存
- python - 自动页面刷新停止工作(Flask App)
- python - django.db.utils.IntegrityError:NOT NULL 约束失败:迁移后 new__post_user_post.user_id 错误
- sql - 数量变化时的折扣礼物更新
- javascript - 为什么 Swup.js 不处理相对链接?
- apache-nifi - apache Nifi:updateAttribute 处理器
- python - 如何访问第 n 个嵌套列表,我是否知道嵌套列表的深度?
- python - 在 AWS Lambda 上,Openpyxl 不跟踪图像