从命令行创建文件
Create a File From Command Line
这可能不是个好主意,但我想和内联汇编一起练习我的汇编。在弄清楚如何读取命令行参数并使用它们创建文件后 here,我将代码转换为 C++ 中的内联汇编。似乎所有的传输都很好(没有编译警告或段错误),但程序什么也没做。下面的代码和 objdump
。知道为什么它不执行语句吗?
编辑:程序应该使用 argv1 中给定的文件名创建文件。
编辑 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 , %rcx;" /* If argc = 2 (argv[0 & argv[1] exist) */
"jne exit;" /* If it's not 2, exit */
"add , %rsp;" /* Move stack pointer to argv[1] */
"pop %rsi;" /* Pop off stack */
"mov %rsi, %rdi;" /* Move argv[1] to rdi */
"mov , %rax;" /* #define __NR_creat 85 */
"mov [=11=]x2E8, %rsi;" /* move 744 to rsi */
"syscall;"
"jmp exit;"
);
asm( "exit:\n\t"
"mov , %rax;"
"mov , %rdi;"
"syscall"
);
}
对象转储:
0000000000400292 <_start>:
400292: 55 push %rbp
400293: 48 89 e5 mov %rsp,%rbp
400296: 59 pop %rcx
400297: 48 83 f9 02 cmp [=12=]x2,%rcx
40029b: 75 1a jne 4002b7 <exit>
40029d: 48 83 c4 08 add [=12=]x8,%rsp
4002a1: 5e pop %rsi
4002a2: 48 89 f7 mov %rsi,%rdi
4002a5: 48 c7 c0 55 00 00 00 mov [=12=]x55,%rax
4002ac: 48 c7 c6 e8 02 00 00 mov [=12=]x2e8,%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 , %rcx;" /* If argc = 2 (argv[0 & argv[1] exist) */
"jne exit;" /* If it's not 2, exit */
"add , %rsp;" /* Move stack pointer to argv[1] */
"pop %rdi;" /* Pop off stack */
"mov , %rax;" /* #define __NR_creat 85 */
"mov [=11=]x2E8, %rsi;" /* move 744 to rsi */
"syscall;"
"exit:;"
"mov , %rax;" /* sys_exit */
"mov , %rdi;"
"syscall"
);
由于声明 C++ 函数的正常过程已被绕过,我们无需担心编译器会添加序言和结尾代码。
您用于 sys_creat
的文件模式位不正确。你有:
"mov [=12=]x2E8, %rsi;" /* move 744 to rsi */
0x2E8 = 744 十进制。我相信您的意图是将 744 八进制放入 %RSI。 744 八进制是 0x1e4。为了使其更具可读性,您可以在 GAS 中使用八进制值,方法是在值前加上 0。这正是您要查找的内容:
"mov 44, %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 , (%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 , %eax;" /* #define __NR_creat 85 */
"mov 44, %esi;" /* File mode octal 744 (rwxr--r--) */
"syscall;"
"exit:;"
"mov , %eax;" /* sys_exit */
"mov , %edi;"
"syscall"
);
在最后一个代码片段中,我还更改为在某些情况下使用 32 位寄存器。您可以利用以下事实:在 x86-64 代码中,将值放入 32 位寄存器会自动将零扩展到 64 位寄存器的高 32 位。这可以在指令编码上节省几个字节。
通过 main w/64-bit 代码访问命令行参数
如果您使用C/C++运行时编译,运行时将提供一个标签_start
用于程序启动、修改命令行参数由 OS 传递以适应 64-bit System V ABI。参数传递在 3.2.3 节中讨论。特别是 64 位代码中 main
的前两个参数是通过 RDI 和 RSI 传递的。 RDI 将包含值 argc
,而 RSI 将包含一个指向 char *
指针数组的指针。由于这些参数不是通过堆栈传递的,因此我们不需要关心任何函数序言和结尾代码。
int main(int argc, char *argv[])
{
asm ( "cmp , %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 , %eax;" /* #define __NR_creat 85 */
"mov 44, %esi;" /* File mode octal 744 (rwxr--r--) */
"syscall;"
"exit:;"
"mov , %eax;" /* sys_exit */
"mov , %edi;"
"syscall"
);
}
你应该可以编译它:
g++ -o testargs testargs.c -g
特别说明:如果您打算最终使用内联汇编以及 C/C++ 代码,您将必须了解 GCC extended assembler templates、约束、破坏等。这超出了这个问题的范围。与创建单独的汇编代码对象并从 C/C++ 调用它们相比,如果使用内联汇编,学习汇编程序要困难得多。 GCC的扩展内联汇编很容易使用不当。代码一开始似乎可以工作,但随着程序变得越来越复杂,细微的错误可能会悄悄出现。
是的,使用 GCC 内联汇编来学习汇编是个坏主意。
除非约束指定,否则内联汇编开始时的寄存器值未定义。函数中的第一条语句 不是 异常。有关内联汇编的文档,请参阅 GCC manual.
在这种特殊情况下,编译器添加了函数序言:
0000000000400292 <_start>:
400292: 55 push %rbp
400293: 48 89 e5 mov %rsp,%rbp
所以现在栈顶不再是argc,而是程序启动时RBP
的值。
这可能不是个好主意,但我想和内联汇编一起练习我的汇编。在弄清楚如何读取命令行参数并使用它们创建文件后 here,我将代码转换为 C++ 中的内联汇编。似乎所有的传输都很好(没有编译警告或段错误),但程序什么也没做。下面的代码和 objdump
。知道为什么它不执行语句吗?
编辑:程序应该使用 argv1 中给定的文件名创建文件。
编辑 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 , %rcx;" /* If argc = 2 (argv[0 & argv[1] exist) */
"jne exit;" /* If it's not 2, exit */
"add , %rsp;" /* Move stack pointer to argv[1] */
"pop %rsi;" /* Pop off stack */
"mov %rsi, %rdi;" /* Move argv[1] to rdi */
"mov , %rax;" /* #define __NR_creat 85 */
"mov [=11=]x2E8, %rsi;" /* move 744 to rsi */
"syscall;"
"jmp exit;"
);
asm( "exit:\n\t"
"mov , %rax;"
"mov , %rdi;"
"syscall"
);
}
对象转储:
0000000000400292 <_start>:
400292: 55 push %rbp
400293: 48 89 e5 mov %rsp,%rbp
400296: 59 pop %rcx
400297: 48 83 f9 02 cmp [=12=]x2,%rcx
40029b: 75 1a jne 4002b7 <exit>
40029d: 48 83 c4 08 add [=12=]x8,%rsp
4002a1: 5e pop %rsi
4002a2: 48 89 f7 mov %rsi,%rdi
4002a5: 48 c7 c0 55 00 00 00 mov [=12=]x55,%rax
4002ac: 48 c7 c6 e8 02 00 00 mov [=12=]x2e8,%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 , %rcx;" /* If argc = 2 (argv[0 & argv[1] exist) */
"jne exit;" /* If it's not 2, exit */
"add , %rsp;" /* Move stack pointer to argv[1] */
"pop %rdi;" /* Pop off stack */
"mov , %rax;" /* #define __NR_creat 85 */
"mov [=11=]x2E8, %rsi;" /* move 744 to rsi */
"syscall;"
"exit:;"
"mov , %rax;" /* sys_exit */
"mov , %rdi;"
"syscall"
);
由于声明 C++ 函数的正常过程已被绕过,我们无需担心编译器会添加序言和结尾代码。
您用于 sys_creat
的文件模式位不正确。你有:
"mov [=12=]x2E8, %rsi;" /* move 744 to rsi */
0x2E8 = 744 十进制。我相信您的意图是将 744 八进制放入 %RSI。 744 八进制是 0x1e4。为了使其更具可读性,您可以在 GAS 中使用八进制值,方法是在值前加上 0。这正是您要查找的内容:
"mov 44, %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 , (%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 , %eax;" /* #define __NR_creat 85 */
"mov 44, %esi;" /* File mode octal 744 (rwxr--r--) */
"syscall;"
"exit:;"
"mov , %eax;" /* sys_exit */
"mov , %edi;"
"syscall"
);
在最后一个代码片段中,我还更改为在某些情况下使用 32 位寄存器。您可以利用以下事实:在 x86-64 代码中,将值放入 32 位寄存器会自动将零扩展到 64 位寄存器的高 32 位。这可以在指令编码上节省几个字节。
通过 main w/64-bit 代码访问命令行参数
如果您使用C/C++运行时编译,运行时将提供一个标签_start
用于程序启动、修改命令行参数由 OS 传递以适应 64-bit System V ABI。参数传递在 3.2.3 节中讨论。特别是 64 位代码中 main
的前两个参数是通过 RDI 和 RSI 传递的。 RDI 将包含值 argc
,而 RSI 将包含一个指向 char *
指针数组的指针。由于这些参数不是通过堆栈传递的,因此我们不需要关心任何函数序言和结尾代码。
int main(int argc, char *argv[])
{
asm ( "cmp , %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 , %eax;" /* #define __NR_creat 85 */
"mov 44, %esi;" /* File mode octal 744 (rwxr--r--) */
"syscall;"
"exit:;"
"mov , %eax;" /* sys_exit */
"mov , %edi;"
"syscall"
);
}
你应该可以编译它:
g++ -o testargs testargs.c -g
特别说明:如果您打算最终使用内联汇编以及 C/C++ 代码,您将必须了解 GCC extended assembler templates、约束、破坏等。这超出了这个问题的范围。与创建单独的汇编代码对象并从 C/C++ 调用它们相比,如果使用内联汇编,学习汇编程序要困难得多。 GCC的扩展内联汇编很容易使用不当。代码一开始似乎可以工作,但随着程序变得越来越复杂,细微的错误可能会悄悄出现。
是的,使用 GCC 内联汇编来学习汇编是个坏主意。
除非约束指定,否则内联汇编开始时的寄存器值未定义。函数中的第一条语句 不是 异常。有关内联汇编的文档,请参阅 GCC manual.
在这种特殊情况下,编译器添加了函数序言:
0000000000400292 <_start>:
400292: 55 push %rbp
400293: 48 89 e5 mov %rsp,%rbp
所以现在栈顶不再是argc,而是程序启动时RBP
的值。