x86 NASM将字符串转换为整数

时间:2017-04-20 02:04:29

标签: assembly nasm x86-64

这是一个简单的问题,但它让我头晕目眩。我需要将一串字符(输入为负十进制数)转换为无符号整数。 rdi寄存器保存要转换的字符串。 rax寄存器将保存结果。

public bool IsInBetween(string target, string start, string end)
{
    DateTime targetDate = DateTime.Parse(target);
    DateTime startDate = DateTime.Parse(start);
    DateTime endDate = DateTime.Parse(end);

    return startDate <= targetDate && targetDate <= endDate;
}
  1. 我需要迭代每个字符,我试图通过使用rsi寄存器来使用它。但每次我尝试这个时,都会出现分段错误。

  2. 无效的组合错误。我知道这是因为寄存器的大小不同,但我不知道如何继续将转换后的ascii值添加回rax。

  3. 这里有一个类似的问题帮助我更好地理解了这个过程,但是我遇到了障碍: Convert string to int. x86 32 bit Assembler using Nasm

1 个答案:

答案 0 :(得分:2)

  

我需要迭代每个字符,我试图通过使用rsi寄存器来使用它。但每次尝试这个时,我都会遇到分段错误。

根据您显示的代码以及RDI保存字符串开头地址的语句,我可以看到为什么您会在该负载中遇到分段错误的几个不同原因

问题可能是RDI包含8个字符的ASCII字符串(按值传递),而不是包含字符串的内存位置的地址(通过引用传递)?

另一个更可能的可能性是它在循环的前几次迭代中工作正常,但是你开始尝试读取超过字符串的结尾,因为你没有正确地终止循环。您显示的代码中没有dtoi_end标签,也没有实际跳转到convert_end标签的位置。这些应该是相同的标签吗?如果我传入字符串“-2”会发生什么?你的循环何时终止?看起来我不喜欢!

您需要某种方式来指示整个字符串已被处理。有几种常见的方法。一种是在字符串的末尾使用一个sentinel终结符,就像C与ASCII NUL字符一样。在循环内部,您将检查正在处理的字符是否为0(NUL),如果是,则跳出循环。另一种选择是将字符串的长度作为函数的附加参数传递,就像Pascal对count-length字符串一样。然后,你将在循环内部进行测试,检查你是否已经处理了足够多的字符,如果是,则跳出循环。

我会尽量不要过于讲究这个问题,但你应该能够通过使用调试器来自己检测这个问题。逐行执行代码,观察变量/寄存器的值,并确保您了解正在发生的事情。这基本上就是我在分析你的代码时所做的,除了我用我的头作为调试器,在我自己的脑海中“执行”代码。但是,让计算机更容易(并且不易出错),这也就是发明调试器的原因。如果您的代码无效,并且您没有在调试器中逐行执行,那么您还没有努力工作来自己解决问题。实际上,单步执行您编写的每个函数是一个很好的习惯,因为(A)它将确保您理解您所写内容的逻辑,以及(B)它我会帮你找到虫子。

  

无效的组合错误。我知道这是因为寄存器的大小不同,但是如果继续将转换后的ascii值添加回rax,我很遗憾。

你必须使尺寸匹配。您可以执行add al, dl,但之后您将结果限制为8位BYTE。这可能不是你想要的。因此,您需要将dl转换为64位QWORD,例如rax。显而易见的方法是使用MOVZX指令,该指令执行零扩展。换句话说,它将值“扩展”为更大的大小,将高位填充为0。这就是你想要的无符号值。对于签名值,您需要执行符号识别扩展(即将符号位考虑在内),为此,您将使用MOVSX指令。

在代码中:

movzx  rdx, dl
add    rax, rdx

请注意,正如其中一位评论者指出的那样,DL只是RDX寄存器的最低8位:

| 63 - 32 | 31 - 16 | 15 - 8 | 7 - 0 |
--------------------------------------
                    |   DH   |   DL  |
--------------------------------------
          |           EDX            |
--------------------------------------
|                 RDX                |

因此,xor dl, dlxor rdx, rdx是多余的。后者完成了前者。此外,每次修改dl时,实际上都在修改rdx的最低8位,这将导致不正确的结果。提示,提示:这是你已经捕获的其他东西(虽然你可能不明白为什么!)通过单步执行调试器。

此外,根本不需要xor rdx, rdx!您可以通过xor edx, edx完成相同的任务more efficiently

只是为了好玩,这里有一个可能的代码实现:

; Parameters: RDI == address of start of character string
;             RCX == number of characters in string
; Clobbers:   RDX, RSI
; Returns:    result is in RAX

    xor   esi, esi

convert:
    ; See if we've done enough characters by checking the length of the string
    ; against our current index.
    cmp   rsi, rcx
    jge   convert_end

    ; Get the next character from the string.
    mov   dl, BYTE [rdi + rsi]

    cmp   dl, "-"
    je    increment

    cmp   dl, "."
    je    convert_end

    ; Efficient way to multiply by 10.
    ; (Faster and less difficult to write than the MUL instruction.)
    add   rax, rax
    lea   rax, [4 * rax + rax]

    sub   dl, "0"
    movzx rdx, dl
    add   rax, rdx

    ; (fall through to increment---no reason for redundant instructions!)

increment:
    inc   rsi            ; increment index/counter
    jmp   convert        ; keep looping

convert_end:
    ret

(警告:这个逻辑未经测试!我只是以更优化的方式重写了现有代码,没有错误。)