【问题标题】:General structure for executing system commands from x86-64 assembly (NASM)?从 x86-64 程序集(NASM)执行系统命令的一般结构?
【发布时间】:2015-02-20 21:34:44
【问题描述】:

我正在尝试在汇编中进行一些基本的系统调用(OSX 上的 NASM 中的 x86-64),但到目前为止还没有成功。

到目前为止,我在网上看到的唯一示例是从标准输入读取或写入标准输出,例如:

global main
section .text

main:
  call write

write:
  mov rax, 0x2000004
  mov rdi, 1
  mov rsi, message
  mov rdx, length
  syscall

section .data
message: db 'Hello, world!', 0xa
length: equ $ - message

但是,当我尝试使用相同的模式进行另一个系统调用时,它不起作用(它说Bus error: 10):

global main
section .text

main:
  call mkdir

mkdir:
  mov rax, 0x2000136 ; mkdir system command number
  mov rdi, rax ; point destination to system command
  mov rsi, directory ; first argument
  mov rdx, 755 ; second argument
  syscall

section .data
directory: db 'tmp', 0xa

调用系统命令的一般结构是什么(理想情况下在 NASM 中的 OSX 上)?

基本上你应该做的是在这里找到你想要的系统调用:http://www.opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master。所以“写”的看起来像这样:

4   AUE_NULL    ALL { user_ssize_t write(int fd, user_addr_t cbuf, user_size_t nbyte); } 

也就是说:

  • 系统调用号:4
  • 参数数量:3(文件描述符、字符串/缓冲区的内存地址、缓冲区长度)

所以我开始认为一般模式是这样的:

rax: system call number
rdi: maybe? point to system call ("destination index"), but why the `1` in the write example?
rsi: first argument to system call ("source index", the string in this case)
rdx: second argument to system call
rcx: third argument (if necessary, but not in the system write case)

那么就好像您可以直接映射任何系统命令。所以mkdir:

136 AUE_MKDIR ALL { int mkdir(user_addr_t path, int mode); } 

将被翻译成:

rax: 0x20000136 ; 136 + 20000000
rdi: i dunno, maybe `rax`?
rsi: directory (first argument)
rdx: 755 (mode, second argument)

但是是的,那行不通。

我做错了什么?如何执行此操作的一般模式是什么,以便我可以在syscalls.master 中的任何其他系统命令上对其进行测试?你能描述一下不同寄存器在这里扮演的角色吗?这将有助于澄清我的想法。

【问题讨论】:

  • 我不会假装对 OSX 有任何了解,但 write 调用中的 '1' 可能是标准输出的文件描述符。这告诉我 rdi 是第一个应该是目录的参数,而 rsi 是第二个应该是模式的参数。如果我错了,请告诉我,我会闭嘴。
  • 是的,我不知道,你是怎么弄清楚这些东西的? (比如有不同操作系统的文件描述符值列表吗?)
  • Posix 标准 stdout = 1,stderr = 2,stdin = 0。http://pubs.opengroup.org/onlinepubs/009695399/basedefs/unistd.h.html.

标签: macos assembly nasm x86-64


【解决方案1】:

我相信 OSX 遵循标准的 SYSV ABI 调用约定,至少您的示例肯定是这样的。参数按顺序进入寄存器RDI、RSI、RDX、R10、R8 和R9。系统调用号进入RAX。

我们看看write:int fd, user_addr_t cbuf, user_size_t nbyte 大会:

mov rdi, 1       ; fd = 1 = stdout 
mov rsi, message ; cbuf
mov rdx, length  ; nbyte

现在,对于mkdir:user_addr_t path, int mode 显然你需要将path 放入rdi 和mode 放入rsi。

mkdir:
  mov rax, 0x2000136 ; mkdir system command number
  mov rdi, directory ; first argument
  mov rsi, 0x1ED     ; second argument, 0x1ED = 755 octal
  syscall
  ret

请注意,您需要ret 和mkdir 子程序的结尾,并且您还需要一个,这样您的main 就不会落入mkdir。此外,您可能应该使用lea 来加载directory 参数,并使用RIP-相对寻址,例如lea rdi, [rel directory]。

【讨论】:

  • 嗯,现在总线错误消失了,但我没有看到生成的目录。还有什么问题吗? gist.github.com/lancejpollard/a84ca2d67e98a4be1788
  • 您需要使用0 而不是0xa(换行符)来终止您的目录名称。
  • 似乎也不起作用:/ 你怎么知道这些值应该是什么? (有相关文档吗?还没有找到任何描述db 的最后一个参数是如何工作的)。
  • C 和系统调用中的字符串以零结尾。您可能遇到了不同的问题。在系统调用之后检查rax 中的返回值(这是一个错误代码)。还要检查所有父目录是否存在,因为mkdir 只会创建最终组件。
  • 显然dtruss 是strace 的os x 等价物,所以通过它运行你的程序。它应该记录您实际调用的系统调用、您传递的参数以及结果。
【解决方案2】:

您几乎说对了:系统调用号需要 0x88(十二月 136)。 syscall.master 中的系统调用是十进制的。你最终调用了getsid(即系统调用310)。

对于参数,不要使用 syscalls.master ,因为它会给你内核视角,这有点歪曲(当涉及到参数名称时)。您应该使用 /usr/include/unistd.h 作为原型,使用 usr/inclunde/sys/syscall.h 作为数字。 syscalls.master 仅在系统调用未导出到这些文件的情况下派上用场,并且主文件显示 NO_SYSCALL_STUB。

ABI 与 System V AMD64 ABI 相同。 http://people.freebsd.org/~obrien/amd64-elf-abi.pdf

您可以像 libsystem 一样查看系统调用:

otool -tV /usr/lib/system/libsystem_kernel.dylib  | more 
# seek to /^_mkdir:
_mkdir:
0000000000012dfc        movl    $0x2000088, %eax
0000000000012e01        movq    %rcx, %r10
0000000000012e04        syscall
0000000000012e06        jae     0x12e0d
0000000000012e08        jmpq    cerror_nocancel
0000000000012e0d        ret
0000000000012e0e        nop
0000000000012e0f        nop

所有系统调用本质上都有以下结构:

  1. 此时的参数已按上述方式放入 RDI、RSI、... ABI
  2. 系统调用 # 已加载到 EAX。 0x2 意味着 POSIX 系统调用。 0x1 将是一个马赫陷阱,0x3 - 特定于拱门,0x4 - 诊断系统调用
  3. rcx 保存到 r10
  4. 系统调用被执行

    >

    1. 内核模式返回用户模式,经过系统调用指令
    2. EAX 现在保存系统调用返回值,所以 “jae”表示如果系统调用返回值 >=0 - 即 ok - 继续“ret”并返回给用户
    3. 如果没有,跳转到 cerror_nocancel 加载 errno 的值并返回 -1 到 用户。

【讨论】:

    【解决方案3】:

    Bus error: 10 错误似乎是由不正确的系统调用号和没有退出系统调用引起的。

    ; nasm -f macho64 mkdir.asm && ld -o mkdir mkdir.o && ./mkdir
    
    %define SYSCALL_MKDIR 0x2000088
    %define SYSCALL_EXIT  0x2000001
    
    global start
    
    section .text
    start:
      call mkdir
      call exit
      ret
    
    mkdir:
      mov rax, SYSCALL_MKDIR
      mov rdi, directory
      mov rsi, 0x1ED
      syscall
    
    exit:
      mov rax, SYSCALL_EXIT
      mov rdi, 0
      syscall
    
    section .data
    directory: db 'tmp', 0
    

    对原代码的改动总结:

    • 将main 符号重命名为start
    • 将 mkdir 系统调用号从 0x2000136 更改为 0x2000088
    • 更改注册表分配
    • 将目录变量中的0xa 字符更改为0(不使用但会导致文件名不正确)

    NASM

    我还必须安装 nasm 的 2.10.09 版本:

    brew install https://raw.githubusercontent.com/Homebrew/homebrew/c1616860c8697ffed8887cae8088ab39141f0308/Library/Formula/nasm.rb
    brew switch nasm 2.10.09
    

    这是由于:

    • /usr/bin/nasm 不支持 nacho64
    • 最新的 brew 版本 (2.11.08) 导致此错误:fatal: No section for index 2 offset 0 found

    【讨论】:

    • fatal: No section for index 2 offset 0 found 与记录在案的 bug 相关,特别是 2.11.08。一段时间以来,我们一直在处理关于 SO 的问题。
    猜你喜欢
    • 1970-01-01
    • 2013-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-20
    • 1970-01-01
    相关资源
    最近更新 更多