这可能是一个坏主意,但我想练习我的装配和内联装配。在我弄清楚如何读取命令行参数并使用它们here创建文件之后,我将代码转换为C ++中的内联汇编。似乎所有人都转移得很好(没有编译警告或段错误),但程序绝对没有。代码和objdump
以下。有没有想过它为什么不执行这些陈述?
编辑:程序应使用argv 1中提供的文件名创建文件。
编辑2:Intel(R)Core(TM)i7-4710HQ 64位CPU @ 2.50GHz
编译完成:
g++ -o args args.cpp -g -nostartfiles
代码:
extern "C" void _start();
void _start(){
asm ( "pop %rcx;" /* Contains argc */
"cmp $2, %rcx;" /* If argc = 2 (argv[0 & argv[1] exist) */
"jne exit;" /* If it's not 2, exit */
"add $8, %rsp;" /* Move stack pointer to argv[1] */
"pop %rsi;" /* Pop off stack */
"mov %rsi, %rdi;" /* Move argv[1] to rdi */
"mov $85, %rax;" /* #define __NR_creat 85 */
"mov $0x2E8, %rsi;" /* move 744 to rsi */
"syscall;"
"jmp exit;"
);
asm( "exit:\n\t"
"mov $60, %rax;"
"mov $1, %rdi;"
"syscall"
);
}
objdump的:
0000000000400292 <_start>:
400292: 55 push %rbp
400293: 48 89 e5 mov %rsp,%rbp
400296: 59 pop %rcx
400297: 48 83 f9 02 cmp $0x2,%rcx
40029b: 75 1a jne 4002b7 <exit>
40029d: 48 83 c4 08 add $0x8,%rsp
4002a1: 5e pop %rsi
4002a2: 48 89 f7 mov %rsi,%rdi
4002a5: 48 c7 c0 55 00 00 00 mov $0x55,%rax
4002ac: 48 c7 c6 e8 02 00 00 mov $0x2e8,%rsi
4002b3: 0f 05 syscall
4002b5: eb 00 jmp 4002b7 <exit>
答案 0 :(得分:3)
这很糟糕。您可能已经注意到编译器为函数push %rbp
发出了函数序言mov %rsp,%rbp
_start
:
400292: 55 push %rbp
400293: 48 89 e5 mov %rsp,%rbp
如果您要执行此操作,请至少考虑使用-fomit-frame-pointer
进行编译。使用函数序言推送 RBP ,当您弹出 RCX 时,您没有将命令行参数的数量放入 RCX ,而是放置 RBP (现在位于堆栈顶部)的值到 RCX 。当然,这会对你的其他堆栈操作进行级联处理错误的值。
您可以直接编码_start
函数,而不是将堆栈框架省略为我的第一个建议:
asm ( ".global _start;" /* Make start symbol globally visible */
"_start:;"
"pop %rcx;" /* Contains argc */
"cmp $2, %rcx;" /* If argc = 2 (argv[0 & argv[1] exist) */
"jne exit;" /* If it's not 2, exit */
"add $8, %rsp;" /* Move stack pointer to argv[1] */
"pop %rdi;" /* Pop off stack */
"mov $85, %rax;" /* #define __NR_creat 85 */
"mov $0x2E8, %rsi;" /* move 744 to rsi */
"syscall;"
"exit:;"
"mov $60, %rax;" /* sys_exit */
"mov $2, %rdi;"
"syscall"
);
由于已经绕过了声明 C ++ 函数的正常过程,我们无需担心编译器添加序言和结尾代码。
用于sys_creat
的文件模式位不正确。你有:
"mov $0x2E8, %rsi;" /* move 744 to rsi */
0x2E8 = 744十进制。我相信你的意图是将744八进制加入%RSI 。 744八进制是0x1e4。为了使其更具可读性,您可以在 GAS 中使用八进制值,方法是将值预先设置为0.这可能是您所寻找的:
"mov $0744, %rsi;" /* File mode octal 744 (rwxr--r--) */
而不是:
"pop %rsi;" /* Pop off stack */
"mov %rsi, %rdi;" /* Move argv[1] to rdi */
您可以直接弹出%rdi
:
"pop %rdi;" /* Pop off stack */
您也可以将参数保留在堆栈中并以这种方式直接访问它们:
asm ( ".global _start;" /* Make start symbol globally visible */
"_start:;"
"cmp $2, (%rsp);" /* If argc = 2 (argv[0 & argv[1] exist) */
"jne exit;" /* If it's not 2, exit */
"mov 16(%rsp), %rdi;" /* Get pointer to argv[1] */
"mov $85, %eax;" /* #define __NR_creat 85 */
"mov $0744, %esi;" /* File mode octal 744 (rwxr--r--) */
"syscall;"
"exit:;"
"mov $60, %eax;" /* sys_exit */
"mov $1, %edi;"
"syscall"
);
在最后一段代码片段中,我还改为在某些情况下使用32位寄存器。您可以利用以下事实:在x86-64代码中,将值自动置于32位寄存器中,将值扩展为64位寄存器的高32位。这可以在指令编码上节省几个字节。
如果使用 C / C ++ 运行时进行编译,运行时将提供执行程序启动的标签_start
,修改操作系统传递的命令行参数以适应{{ 3}}。参数传递将在3.2.3节中讨论。特别是64位代码中main
的前两个参数通过 RDI 和 RSI 传递。 RDI 将包含值argc
, RSI 将包含指向char *
指针数组的指针。由于这些参数不通过堆栈传递,因此我们不需要关心任何函数序言和结尾代码。
int main(int argc, char *argv[])
{
asm ( "cmp $2, %rdi;" /* If argc = 2 (argv[0 & argv[1] exist) */
"jne exit;" /* If it's not 2, exit */
/* _RSI_ (second arg to main) is a pointer
to an array of character pointers */
"mov 8(%rsi), %rdi;"/* Get pointer to second char * pointer in argv[] */
"mov $85, %eax;" /* #define __NR_creat 85 */
"mov $0744, %esi;" /* File mode octal 744 (rwxr--r--) */
"syscall;"
"exit:;"
"mov $60, %eax;" /* sys_exit */
"mov $1, %edi;"
"syscall"
);
}
你应该可以用以下代码编译它:
g++ -o testargs testargs.c -g
特别说明:如果您打算最终使用内联汇编以及 C / C ++ 代码,您将需要了解64-bit System V ABI,约束,这是一个超出这个问题范围的问题。与创建单独的汇编代码对象并从 C / C ++ 中调用它们相比,如果使用内联汇编,学习汇编器要困难得多。非常容易使用GCC的扩展内联组件。代码似乎最初可能起作用,但随着程序变得越来越复杂,细微的错误就会蔓延开来。
答案 1 :(得分:1)
是的,使用GCC内联汇编来学习汇编是个坏主意。
除了约束条件指定外,内联汇编开头的寄存器值未定义。函数中的第一个语句是不异常。有关内联汇编的文档,请参阅GCC manual。
在这种特殊情况下,编译器添加了函数序言:
0000000000400292 <_start>:
400292: 55 push %rbp
400293: 48 89 e5 mov %rsp,%rbp
所以现在堆栈的顶部不再是argc,而是程序启动时RBP
的值。