从命令行创build一个文件

这可能是一个坏主意,但我想练习我的程序集内嵌组装。 在我想出如何读取命令行参数并在这里使用它们创build文件之后,我将代码转换为C ++中的内联汇编。 它似乎都转移罚款(没有编译警告或段错误),但程序没有什么。 下面的代码和objdump 。 任何想法,为什么它不执行的声明?

编辑:程序应该使用argv 1中给出的文件名创build一个文件。

编辑2:Intel(R)Core(TM)i7-4710HQ 64bit 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> 

这不好。 您可能已经注意到,函数序言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位。 这可以在指令编码上节省几个字节。


通过主要的w / 64位代码访问命令行参数

如果使用C / C ++运行时进行编译,则运行时将提供一个执行程序启动的标签_start ,修改OS传递的命令行参数以适应64位System V ABI 。 参数传递在3.2.3节中讨论。 特别是在64位代码中main两个参数是通过RDIRSI传递的。 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 ++代码,你将不得不学习GCC扩展汇编模板 ,约束,clobbering等。这超出了这个问题的范围。 如果使用内联汇编,与创建单独的汇编代码对象并从C / C ++调用汇编代码对象相比,学习汇编器要困难得多。 使用GCC的扩展内联汇编是非常容易的。 代码可能起初似乎起作用,但随着程序变得越来越复杂,细微的错误可能会蔓延。

是的,使用GCC内联汇编学习汇编是一个坏主意。

除了约束指定外,内联汇编开始处的寄存器值是不确定的。 函数中的第一个语句并不是一个例外。 有关内联装配的文档,请参阅GCC手册 。

在这种特殊情况下,编译器添加了函数序言:

 0000000000400292 <_start>: 400292: 55 push %rbp 400293: 48 89 e5 mov %rsp,%rbp 

所以现在栈顶不再是argc,而是程序启动时RBP的值。