【问题标题】:How to print signed integer in x86 assembly (NASM) on Mac如何在 Mac 上的 x86 程序集 (NASM) 中打印有符号整数
【发布时间】:2019-03-22 05:56:00
【问题描述】:

我在 x86 程序集中发现了一个 implementation 的无符号整数转换,我尝试将其插入,但对程序集很陌生,还没有调试环境,很难理解为什么它不起作用。我还希望它可以处理有符号整数,这样它就可以从系统调用中捕获错误消息。

想知道是否可以显示如何修复此代码以打印有符号整数,而不使用 printf 而是使用 this 答案提供的 strprn。

%define a rdi
%define b rsi
%define c rdx
%define d r10
%define e r8
%define f r9
%define i rax

%define EXIT 0x2000001
%define EXIT_STATUS 0

%define READ 0x2000003 ; read
%define WRITE 0x2000004 ; write
%define OPEN 0x2000005 ; open(path, oflag)
%define CLOSE 0x2000006 ; CLOSE
%define MMAP 0x2000197 ; mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t offset)

; szstr computes the lenght of a string.
; rdi - string address
; rdx - contains string length (returned)
strsz:
  xor     rcx, rcx                ; zero rcx
  not     rcx                     ; set rcx = -1 (uses bitwise id: ~x = -x-1)
  xor     al,al                   ; zero the al register (initialize to NUL)
  cld                             ; clear the direction flag
  repnz scasb                     ; get the string length (dec rcx through NUL)
  not     rcx                     ; rev all bits of negative -> absolute value
  dec     rcx                     ; -1 to skip the null-term, rcx contains length
  mov     rdx, rcx                ; size returned in rdx, ready to call write
  ret

; strprn writes a string to the file descriptor.
; rdi - string address
; rdx - contains string length
strprn:
  push    rdi                     ; push string address onto stack
  call    strsz                   ; call strsz to get length
  pop     rsi                     ; pop string to rsi (source index)
  mov     rax, WRITE              ; put write/stdout number in rax (both 1)
  mov     rdi, 1        ; set destination index to rax (stdout)
  syscall                         ; call kernel
  ret

; mov ebx, 0xCCCCCCCD
itoa:
  xor rdi, rdi
  call itoal
  ret

; itoa loop
itoal:
  mov ecx, eax                    ; save original number

  mul ebx                         ; divide by 10 using agner fog's 'magic number'
  shr edx, 3                      ;

  mov eax, edx                    ; store quotient for next loop

  lea edx, [edx*4 + edx]          ; multiply by 10
  shl rdi, 8                      ; make room for byte
  lea edx, [edx*2 - '0']          ; finish *10 and convert to ascii
  sub ecx, edx                    ; subtract from original number to get remainder

  lea rdi, [rdi + rcx]            ; store next byte

  test eax, eax
  jnz itoal

exit:
  mov a, EXIT_STATUS ; exit status
  mov i, EXIT ; exit
  syscall

_main:
  mov rdi, msg
  call strprn
  mov ebx, -0xCCCCCCCD
  call itoa
  call strprn
  jmp exit

section .text
msg: db  0xa, "  Hello StackOverflow!!!", 0xa, 0xa, 0

通过这项工作,可以将有符号整数正确打印到 STDOUT,因此您可以记录寄存器值。

【问题讨论】:

  • 那里还没有调试环境, 先解决这个问题。我假设lldb 可用于类似于 GDB 的 asm。您需要一个可以突出显示单步执行时更改了哪些寄存器的调试器;这很有用。

标签: macos assembly x86-64 nasm itoa


【解决方案1】:

我对您已经链接的How do I print an integer in Assembly Level Programming without printf from the c library? 的回答表明,将整数作为 ASCII 十进制序列化到内存中会给您一个长度,因此您在这里没有使用strlen 的(自定义版本)。

(你的msg 有一个汇编时常数长度,所以不使用它是愚蠢的。)

要打印有符号整数,请实现以下逻辑:

if (x < 0) {
    print('-');   // or just was_negative = 1
    x = -x;
}
unsigned_intprint(x);

未签名涵盖abs(most_negative_integer) 情况,例如在 8 位 - (-128) 中溢出到 -128 签名。但是,如果您将条件 neg 的结果视为 unsigned,则它是正确的,所有输入都不会溢出。

与其实际打印 - 本身,不如保存起始数字为负数这一事实,并在生成最后一个数字后将 - 粘贴在其他数字前面。对于不是 2 次方的碱基,正常算法只能生成逆序的数字,

我的带有系统调用答案的 x86-64 打印整数将输入视为无符号,因此您应该简单地将其与一些符号处理代码一起使用。它是为 Linux 编写的,但替换 write 系统调用号将使其在 Mac 上工作。它们具有相同的调用约定和 ABI。


顺便说一句,xor al,al 严格来说比xor eax,eax 差,除非您特别想要保留 RAX 的前 7 个字节。只有完整寄存器的异或归零是handled efficiently as a zeroing idiom。

另外,repnz scasb 不快;对于大字符串,每个时钟大约 1 次比较。

对于不超过 16 字节的字符串,您可以使用带有 pcmpeqb / pmovmskb / bsf 的单个 XMM 向量来查找第一个零字节,而无需循环。 (SSE2 是 x86-64 的基准)。

【讨论】:

  • 我还想在以后打印任意动态字符串,以后不打印就做 itoa,这就是我以这种方式构造它的原因。感谢有关特殊说明的有用提示。想知道手动循环是否比repnz scasb 更好。
  • @LancePollard:一次查看 1 个字节的简单手动循环也最多可以在大字符串上运行 1 个字节/时钟。启动开销与最后的分支错误预测确实将repne scasb 与字节循环分开。 SIMD 是大幅加速所必需的,即使对于小字符串也非常有用,尤其是当您知道它是 16 字节对齐时。
  • @LancePollard:就像我建议的那样,如果输入为负数,则不要打印 -,而是将其添加到无符号转换结果中。如果您阅读了我对链接问题的回答,它显然是围绕 itoa 设计成缓冲区的,最后打印作为可选附加项。如果您的意思是为什么要使用strlen,则可以保留指针+长度。 C char* 使用隐式长度字符串,但现代 C++ 代码通常使用显式长度 std::string。在一次复制 1 个字节是垃圾的世界中,丢弃一个字符串长度并稍后必须再次找到它通常是一件坏事。
  • @LancePollard:此外,汇编语言对于未来编写代码来说是错误的。没有编译器可以针对当前的简单情况对其进行优化。了解您如何可以 实现运行时长度查找固然很好,但如果您不需要它,您就不想实际执行它。 asm 的美妙之处在于尽可能简化您的问题,并针对 那个 特定情况优化您的代码。与您在每一步都尝试处理一般情况相比,您的代码将更小,更容易理解。你是编译器/防止膨胀的最后一道防线。
猜你喜欢
  • 1970-01-01
  • 2014-02-21
  • 2022-01-01
  • 2019-07-06
  • 1970-01-01
  • 2011-05-13
  • 2015-09-20
  • 1970-01-01
相关资源
最近更新 更多