在 Apple mac M1 cpu 上的 ARM64 汇编中打开和读取文件时出现问题

问题描述 投票:0回答:1
.section __DATA,__data
    .p2align 2
    buffer:
        .zero 4096

.section __TEXT,__text
.global _main
.build_version macos, 13, 0


.p2align 2
_main:
    // x9: buf ptr
    // x10: file descriptor storage
    // x11: file size in bytes

    //init ptr to buf
    adrp    x9, buffer@PAGE              
    add     x9, x9, buffer@PAGEOFF

    // open file
    adr     x0, file_path                      
    mov     x1, #0
    mov     x2, #444
    mov     x16, #5
    svc     0

    // copy file descriptor to x10
    mov     x10, x0                    

.p2align 2
stream_buffer:
    // make syscall read, file descriptor is in x10                                   
    mov     x0, x10
    mov     x1, x9                      
    mov     x2, #4096
    mov     x16, #3
    svc     0

    // if x0 == 0, exit, no bytes were read
    cmp     x0, #0
    beq     exit
    blt     error

    // store number of bytes read
    mov     x11, x0

    // write to stdout from buffer
    mov     x0, #1
    mov     x1, x9
    mov     x2, x11        
    mov     x16, #4
    svc     0

    b       stream_buffer

.p2align 2
exit:
    // exit with status code 0
    mov     x0, #0                      
    mov     x16, #1
    svc     0

.p2align 2
error:
    mov     x0, #1
    adr     x1, file_not_found_error_string
    mov     x2, #20
    mov     x16, #4
    svc     0
    b       exit

.p2align 2
file_path:
    .asciz "/test.txt"

.p2align 2
file_not_found_error_string:
    .asciz "file was not found.\n"


我正在尝试通过编写一个模拟“cat”linux 命令的简单程序来学习汇编。我使用的是配备 M1 芯片的 MacBook Air 2020。我的程序编译得很好,但是在执行二进制文件时,我遇到了我的程序需要输入,然后它会回显输入的任何内容。我相信我滥用了我的文件描述符。任何帮助表示赞赏。

macos assembly arm apple-m1 arm64
1个回答
0
投票

哦,这太搞笑了。

您的代码最终从 stdin 读取的事实是代码中错误的顶峰,同时还伴随着一些意外的操作系统行为。

让我们先从高层次的角度来看待这个问题:

  1. 您打开
    /test.txt
    进行阅读。
  2. 您最多可以从中读取 4096 个字节。
  3. 您将这些字节写入标准输出。

但是你使用的是arm64 macOS,这意味着除非你不遗余力地搞乱操作系统,否则系统卷是只读的并且

/test.txt
不存在。

所以你的

open
系统调用失败了,但是你没有检测到这一点,因为你没有在那里进行错误检查。不好!
现在,在这种情况下,您可能会假设
x0
-1
,因为这就是从 C 调用时
open()
所做的事情,但这不是系统调用 ABI。如果您查看
/usr/lib/system/libsystem_kernel.dylib
是一个反汇编程序并寻求
___open
,您会看到以下内容:

;-- ___open:
;-- func.00002308:
0x00002308      b00080d2       mov x16, 5
0x0000230c      011000d4       svc 0x80
0x00002310      03010054       b.lo 0x2330
0x00002314      7f2303d5       pacibsp
0x00002318      fd7bbfa9       stp x29, x30, [sp, -0x10]!
0x0000231c      fd030091       mov x29, sp
0x00002320      8a030094       bl sym._cerror
0x00002324      bf030091       mov sp, x29
0x00002328      fd7bc1a8       ldp x29, x30, [sp], 0x10
0x0000232c      ff0f5fd6       retab
0x00002330      c0035fd6       ret

这里的关键部分是

b.lo
。系统调用使用进位标志(NZCV 中的“C”)来指示是否存在错误。这意味着:

  • b.lo
    ->
    x0
    保存文件描述符
  • b.hs
    ->
    x0
    持有
    errno

因此您的系统调用失败并在

x0
中返回错误值。特别是
ENOENT
,因为它找不到您要求的文件。并且
ENOENT
恰好是
2
,因此当您将该错误值传递给下一个系统调用时,您最终会从文件描述符
2
中读取,即 stderr。但现在,因为您从命令行调用了二进制文件,所以文件描述符 0、1 和 2 恰好都是同一个文件描述符,因此在这种情况下从 stderr 读取的行为就像从 stdin 读取一样。

那么如何解决这个问题呢?在第一个

b.hs error
之后添加
svc

然后选择一个实际存在的文件路径。

© www.soinside.com 2019 - 2024. All rights reserved.