上个学期我在系统级编程课程中学习了MIPS汇编,现在一直在研究英特尔和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会产生如此复杂的方式来做这么简单的事情?也许这只是编译器解释数组使用的方式?
我认为我的方法在技术上是正确的,因为它产生相同的输出,但我想确保它是一种“可接受的”方式。