【问题标题】:Create a File From Command Line从命令行创建文件
【发布时间】:2016-07-21 18:02:33
【问题描述】:

这可能是个坏主意,但我想与内联汇编一起练习我的汇编。在我弄清楚如何读取命令行参数并使用它们创建文件here 之后,我将代码转换为 C++ 中的内联汇编。似乎一切都很好(没有编译警告或段错误),但程序绝对没有做任何事情。下面的代码和objdump。关于它为什么不执行语句的任何想法?

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

编辑 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"
      );

}

对象转储:

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>

【问题讨论】:

  • 我强烈建议用 C++ 编写代码并告诉编译器打印代码的汇编语言。这将为您提供一个比较基准。
  • 顺便说一句,汇编语言依赖于处理器。 ARM 处理器的汇编语言不同于 Intel 处理器的汇编语言。因此,当您谈论汇编语言时,请说明处理器。推论是汇编语言代码只能在目标处理器上工作并且不可移植。
  • 请在您的问题中添加对汇编语言的目的或功能的描述。当没有参数传递给函数时,我很难理解为什么将寄存器与 2 进行比较。
  • @ThomasMatthews 更新了顶部的 cmets 和处理器。当程序启动时,命令行参数被压入堆栈。见图 3.9 - x86-64.org/documentation/abi.pdf
  • 如果您想在 ASM 中编写整个函数,请将它们写入 .S 文件中。您仍然可以在其上运行gcc 进行组装和链接。 GNU C 内联 asm 不是为在 asm 中编写整个函数而设计的。 (尽管在 ARM 上,存在 __attribute__((naked)) 以阻止编译器发出任何函数序言或 ret 指令。)另请参阅 the x86 tag wikithis collection of links on how to use GNU inline asm correctly to avoid forcing the compiler to emit instructions it doesn't need

标签: linux assembly g++ x86-64 inline-assembly


【解决方案1】:

这很糟糕。您可能已经注意到函数序言 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"
  );

在最后一个代码 sn-p 中,我还更改为在某些情况下使用 32 位寄存器。您可以利用以下事实:在 x86-64 代码中,将值放入 32 位寄存器会自动将零值扩展到 64 位寄存器的高 32 位。这可以在指令编码上节省几个字节。


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

如果您使用C/C++运行时进行编译,运行时将提供标签_start,用于启动程序,修改操作系统传递的命令行参数以适应64-bit System V ABI。参数传递在 3.2.3 节中讨论。特别是 64 位代码中 main 的前两个参数通过 RDIRSI 传递。 RDI 将包含值 argcRSI 将包含指向 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 extended assembler templates、约束、破坏、等等,这超出了这个问题的范围。与创建单独的汇编代码对象并从 C/C++ 调用它们相比,如果您使用内联汇编来学习汇编程序要困难得多。不恰当地使用 GCC 的扩展内联汇编非常容易。代码一开始似乎可以工作,但随着程序变得越来越复杂,可能会出现一些细微的错误。

【讨论】:

  • 感谢您的解释。 0x2e8 工作得很好。如 objdump 所示,十六进制的 85 是 0x55。如果您确实声明了 main,有什么方法可以获取命令行参数?
  • @user10984587 虽然 0x2e8 似乎可以工作,但请在其上执行 ls -l filename。我怀疑您希望使用的标志是否已设置。假设您声明 main 并删除 -nostartfiles 然后 C/C++ 运行时将提供 _start 标签,它将解析命令参数,然后调用 main使用64-bit SYS V Calling Convention。对于 64 位代码,函数的第一个参数在 _RDI_(argc) 中,第二个在 _RSI_(RSI 将是指向字符指针数组的指针)。这与 _start 获取参数的方式不同
  • @user10984587 参数传递在我上一条评论中链接到的文档的 3.2.3 中进行了介绍。
  • 请记住,在 C/C++ 中,带有命令行参数的 main 定义为 int main(int argc, char *argv[])int main(int argc, char **argv)(两者做同样的事情)
  • @user10984587 :我在答案的底部添加了一些内容,以解决您关于使用main的问题
【解决方案2】:

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

除非约束指定,否则内联汇编开始时的寄存器值是未定义的。函数中的第一条语句不是例外。有关内联汇编的文档,请参阅GCC manual

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

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

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

【讨论】:

    猜你喜欢
    • 2012-06-29
    • 2016-07-05
    • 1970-01-01
    • 1970-01-01
    • 2012-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多