上学期我在系统级编程课程中学习了 MIPS 汇编,现在一直在研究 Intel 和 AMD 架构。
我尝试在 GAS 中编写一个简单的 x86_64 程序调用 printf 并打印 argc 和 argv[0-4] 时遇到了麻烦。为了帮助我理解如何正确执行,我使用“gcc -S”查看 C 源文件“test.c”的汇编程序:
#include <stdio.h>
int main (int argc, char * argv[]) {
printf("%d,%s,%s,%s,%s,%s\n", argc, argv[0], argv[1], argv[2], argv[3], argv[4]);
return 0;
}
“gcc -S -masm=intel test.c”的输出是:
.file "test.c"
.intel_syntax noprefix
.section .rodata
.LC0:
.string "%d,%s,%s,%s,%s,%s\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
sub rsp, 32
mov DWORD PTR [rbp-4], edi
mov QWORD PTR [rbp-16], rsi
mov rax, QWORD PTR [rbp-16]
add rax, 32
mov rsi, QWORD PTR [rax]
mov rax, QWORD PTR [rbp-16]
add rax, 24
mov r8, QWORD PTR [rax]
mov rax, QWORD PTR [rbp-16]
add rax, 16
mov rdi, QWORD PTR [rax]
mov rax, QWORD PTR [rbp-16]
add rax, 8
mov rcx, QWORD PTR [rax]
mov rax, QWORD PTR [rbp-16]
mov rdx, QWORD PTR [rax]
mov eax, DWORD PTR [rbp-4]
mov QWORD PTR [rsp], rsi
mov r9, r8
mov r8, rdi
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.7.2 20121109 (Red Hat 4.7.2-8)"
.section .note.GNU-stack,"",@progbits
老实说,我不认为我完全理解第 18-38 行发生了什么。对我来说,看起来 gcc 在 [rbp-4] 和 [rbp-16] 处存储了指向 argc 和 argv[0] 的指针,然后将 [rbp-16] 作为基点加载到 rax(指向 argv[0] 的指针) ),并添加 8,16,24,... 以使 rax 指向 argv[1,2,3,...],然后将该地址加载到适当的寄存器中以传递给 printf。
通过这种解释,我能够充分理解命令行参数是如何传递给 main() 的,以便能够将我的 GAS 代码修复为:
.intel_syntax noprefix
.globl main
.data
fmt: .asciz "%d,%s,%s,%s,%s,%s\n"
.text
main:
push rbp
mov rbp, rsp
mov rdx, QWORD PTR [rsi]
mov rcx, QWORD PTR [rsi+8]
mov r8, QWORD PTR [rsi+16]
mov r9, QWORD PTR [rsi+24]
push [rsi+32]
mov rsi, rdi
mov rdi, offset fmt
xor rax, rax
call printf
return:
mov rsp, rbp
pop rbp
xor rax, rax
ret
这会产生与 test.c 以及 gcc 生成的 test.s 相同的输出。所以我的问题是......我这样做的方式有什么问题吗?如果不是,为什么 gcc 会生成如此复杂的方法来做如此简单的事情?也许这只是编译器解释数组使用的方式?
我想我的方法在技术上是正确的,因为它产生相同的输出,但我想确保它是一种“可接受的”方法。