【问题标题】:AT&T Assembly carry flagAT&T 大会携带标志
【发布时间】:2017-05-24 09:01:32
【问题描述】:

我再次需要 AT&T 汇编方面的帮助,我已将一些数据加载到内存中,如下所示(十六进制和十进制)。

(gdb) x/8xb &buffer_in
0x8049096:  0x03    0x02    0x10    0x27    0xe8    0x03    0x64    0x00


(gdb) x/8db &buffer_in
0x8049096:   3        2      16      39      -24      3      100      0

假设第一个字节 = 数字计数,第二个 = 每个数字长度(以字节为单位),然后我们得到(第一个 * 第二个)字节的数字。对于此示例,3 个数字,每个 2 个字节,第一个数字是 16 39,依此类推。我想添加每个数字,所以在这种情况下,它会将 0x10 + 0xe8(低字节)添加到 result[0],然后将 0x27 + 0x03 添加到 result[1],然后再添加 result[0] = result[0] + 0x64 最后结果 [1] = 结果 [1] + 0x00。

当我将 0x64 添加到已经包含 0xf8 的结果 [0] 时,CF(进位标志)被设置,这当然很好,因为我想在下一个添加结果 [1] 中使用这个进位。 但问题是在下一条 CMP 指令之后(我将在下面的代码中标记它),此进位标志被清除,因此最终结果为 0x5C2A(当我合并两个字节的结果时)并且应该是 0x5C2B (但由于 cmp 指令,进位标志不影响加法)。

%eax - 要求和的数字数量

%ecx - 每个数字的长度,以字节为单位

%esi - 在循环开始之前指向“真实”数据的第一个字节(在本例中为 0x10)

loop1:

movl $0, %ebx

    loop2:
    leal (%esi, %ebx, 1), %edi
    movb (%edi), %dl  # %dl contain now next byte to add
    adc %dl, result(%ebx) # adding to result

    inc %ebx
    cmp %ebx, %ecx # this comparsion clears CF flag and that's the problem
    JG loop2

leal (%esi, %ecx, 1), %esi

dec %al
cmp $0, %al
JG loop1

【问题讨论】:

  • pushf/popf 对你有用吗?例如,在 loop2 标签之前执行 pushf,在它之后执行 popf,然后在 cmp 指令之前再次 pushf。这应该保存/恢复状态(标志)寄存器,从而在 cmp 中保留进位标志..
  • @Sokre 它正在工作,也只是尝试 pushf popf 但您的正确解决方案首先出现!谢谢哥们!

标签: assembly flags att carryflag


【解决方案1】:

这通常通过调整算法逻辑来解决,以避免在 addadc 之间出现任何 CF 更改指令,当您想要循环动态字节数时,乍一看这可能看起来有点不可能。

但是,如果您阅读有关指令 INCDEC 的详细信息,就会发现一件有趣的事情,它看起来像是奇怪的不一致。他们不影响CF! (它实际上是因为类似的用例而设计的,比如这个)。

所以你的代码可能看起来像这样(对不起 Intel+NASM 语法,我不喜欢 AT&T,所以你自己转换,至少你会确定你理解它)(另外我没有调试它,所以它可能有一些错误,如果有问题,请告诉我):

    ; zero the result data first
    movzx    edx,byte [buffer_in+1]  ; element length
zero_result:
    dec      edx
    mov      [result+edx],byte 0
    jnz      zero_result
    ; now sum all elements
    movzx    ecx,byte [buffer_in+0]  ; number of elements
    lea      esi,[buffer_in+2]       ; source data ptr
elements_loop:
    movzx    edx,byte [buffer_in+1]  ; element length
    xor      ebx,ebx       ; offset of byte of element = 0 AND CF=0 (!)
element_byte_loop:
    mov      al,[esi]      ; read source byte (no CF change)
    inc      esi           ; ++ptr (CF preserved)
    adc      [result+ebx],al   ; add it to result with CF
    inc      ebx           ; next offset of byte inside element (CF preserved)
    dec      edx           ; do all bytes of element (CF preserved)
    jnz      element_byte_loop
    ; single element added to result, now repeat it for all elements
    dec      ecx
    jnz      elements_loop

【讨论】:

  • plus1 仅使用 Intel 语法。
  • ADOX 被添加用于提高 big int 数学性能,也可用于避免更改 CF
  • @LưuVĩnhPhúc,是的,但这将您的代码限制在一组非常精选的非常新的处理器上。并且有更好的方法来解决这个问题。您可以使循环更小更快,请参阅下面的答案(尤其是最后一部分)。
  • 当然..顺便说一句,这绝不是“生产”质量代码,出于教育原因,它以简单的方式完成。如果我必须在现实世界中做类似的事情,我可能会使用 SIMD 指令动态编译展开的循环,并继续使用它(特别是如果元素的大小对于许多数据集都是固定的)。
【解决方案2】:

如果你只是想保存进位标志,有一些技巧可以做到这一点。

举旗

pushf   //save the flags
...... do stuff
popf    //restore the flags

将 CF 保存在寄存器中

//save CF in eax
sbb eax,eax     //CF=1 -> CF=1, regx=-1; CF=0 -> CF=0, regx=0, clobbers other flags
//note that sbb reg, reg preserves! CF, how cool is that!
.... do stuff, do not alter eax
add eax,1         //restore CF

重写循环,使其向下计数为零

loop1:
  mov ebx,ecx        //ebx = count
  lea esi,[esi+ecx]  //esi = end of buffer
  neg ebx            //ebx = -count
loop2:
  //no need for the lea (the mov can do complex addressing)
  mov dl,[esi+ebx]  # %dl contain now next byte to add
  adc [ecx+ebx+result],dl adding to result
  inc ebx            //ebx will be zero when done :-)
//no need for cmp
  jnz loop2           //we only need ZF

以防万一你错过了。诀窍如下。
首先,我们将计数添加到基指针。
接下来我们否定计数。
因此,循环开始于basepointer+count-count = basepointer
在每次迭代中,我们都会增加 -count
这导致循环迭代中的以下效果naddress = base+count-count+n ergo:adr = base + n
当我们完成时-count+n 将为零,我们不需要执行cmp,因为inc 将根据需要调整ZF 而不会破坏CF

请注意,原则上我只使用 Intel 语法。

【讨论】:

  • lahf 和 sahf 的性能优于 pushf 和 popf。
猜你喜欢
  • 1970-01-01
  • 2015-03-17
  • 1970-01-01
  • 1970-01-01
  • 2019-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多