为什么这个汇编代码失败了?

时间:2014-11-13 05:03:02

标签: c string assembly rotation

我正在尝试在程序集中创建一个程序,将整个字符串旋转一个字节,但是当我尝试测试此代码时,它似乎在旋转时间少于8次时正常工作,但任何事情都超过那它似乎不再起作用,我花了无数个小时试图弄清楚为什么它没有运气, 这是代码:

_rotateLeft:
    pushl %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
    movl    12(%ebp), %esi  #load number of bytes
    subl    $1, %esi        #subtract 1 because we want a number to add to the address of the first char to get the last char.
    addl    %esi, %eax      #adding esi to get the last char in the string (most significant byte)
    movl    $1, %edx
    movl    $0, %edi
    testb   $0x80, (%eax)
    cmove   %edi, %edx      #testing the msb to see if its one or zero so we can rotate it back
    shlb    $1, (%eax)      #shift the most significant byte
    testb   $0x80, -1(%eax) #test the one before it to see if the msb is 0 or 1 so we can move the msb to the most significant byte (to simulate the feel that all string is being shifted)
    jz  L4                  #if 0 then there is not need to put 0 in the lsb because shift already did that for us
    L5:
    orb $1, (%eax)          #if 1 then or it with the most significant byte to turn the lsb to a 1 without changing the whole byte
    L4:
    decl    %esi            #decrement our counter
    decl    %eax            #decrement eax to get the next byte (moving from most significant to least significant)
    shlb    $1, (%eax)      
    movl    $1, %ecx
    movl    $0, %edi
    testb   $0x80, -1(%eax)
    cmove   %edi, %ecx      #if the one before it is a 0 then let ecx equal zero other wise ecx is 1
    orb     %cl, (%eax) 
    cmpl    $1, %esi        #we don't want it to reach the last byte that would be done after
    jne     L4
    decl %eax               #get the last byte
    shlb    $1, (%eax)
    orb     %dl, (%eax)     #or it with the value obtained in line 26
    leave
    ret

以下是应与之关联的c代码:

#include <stdio.h>

#include <stdlib.h> /* _fmode */
#include <fcntl.h> /*  _O_BINARY */

// The following is from http://oldwiki.mingw.org/index.php/binary
unsigned int _CRT_fmode = _O_BINARY; // Needed to put I/O in binary mode

#define BUFFER_SIZE 2048

void rotateLeft(char *, int);
int main(int argc, char **argv) {
    char buffer[BUFFER_SIZE];
    int bytesRead;

    while( (bytesRead = read(0, buffer, BUFFER_SIZE)) > 0) {

        rotateLeft(buffer, bytesRead);
        write(1, buffer, bytesRead); // Does not add newline

    }   

    return 0;

这段代码,当输入为32位时,应该在旋转32次时输出相同的输入,但正如我所说的那样,它在8次后失败,这里有一个小脚本来测试代码:

gcc -Wall -g rotateLeftMain.c rotateLeft.s -o rotateLeftMain
awk 'BEGIN {printf "2345"}' >junkIn.txt

for i in `seq 1 32`;
do
    ./rotateLeftMain.exe < junkIn.txt > junkOut.txt
    mv junkOut.txt junkIn.txt
done    

cat junkIn.txt

2 个答案:

答案 0 :(得分:2)

我想我明白你的代码在做什么。我没有在你的逻辑中发现任何错误。

但是,您犯的一个错误是您的汇编函数不遵循x86调用约定。在x86上,一些寄存器被视为&#34;非易失性&#34;,这意味着您的函数需要保留它们的值。这通常是通过推送堆栈上的寄存器并在返回之前弹出寄存器来完成的。您正在废弃的非易失性寄存器为esiedi。可能是您的main函数将局部变量存储在其中一个寄存器中,当您删除寄存器时,汇编函数后面的write没有给出正确的参数。我不确定这会解决你的整个问题,但这是你需要解决的一件事。

好的,我只是运行你的程序,它对我有用。我修改它以前后打印出来。这可能有助于您确定它在哪里打破:

#include <stdio.h>

#include <stdlib.h> /* _fmode */
#include <fcntl.h> /*  _O_BINARY */

// The following is from http://oldwiki.mingw.org/index.php/binary
unsigned int _CRT_fmode = _O_BINARY; // Needed to put I/O in binary mode

#define BUFFER_SIZE 2048

void rotateLeft(char *, int);
int main(int argc, char **argv)
{
    unsigned char buffer[BUFFER_SIZE];
    int bytesRead;

    while( (bytesRead = read(0, buffer, BUFFER_SIZE)) > 0) {
        fprintf(stderr, "before = %02x %02x %02x %02x\n",
            buffer[0], buffer[1], buffer[2], buffer[3]);
        rotateLeft(buffer, bytesRead);
        fprintf(stderr, "after  = %02x %02x %02x %02x\n",
            buffer[0], buffer[1], buffer[2], buffer[3]);
        write(1, buffer, bytesRead); // Does not add newline
    }   

    return 0;
}

和程序集(稍加修改以添加推送和弹出):

_rotateLeft:
    .globl _rotateLeft
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %esi
    pushl   %edi
    movl    8(%ebp), %eax
    movl    12(%ebp), %esi  #load number of bytes
    subl    $1, %esi        #subtract 1 because we want a number to add to the address of the first char to get the last char.
    addl    %esi, %eax      #adding esi to get the last char in the string (most significant byte)
    movl    $1, %edx
    movl    $0, %edi
    testb   $0x80, (%eax)
    cmove   %edi, %edx      #testing the msb to see if its one or zero so we can rotate it back
    shlb    $1, (%eax)      #shift the most significant byte
    testb   $0x80, -1(%eax) #test the one before it to see if the msb is 0 or 1 so we can move the msb to the most significant byte (to simulate the feel that all string is being shifted)
    jz  L4                  #if 0 then there is not need to put 0 in the lsb because shift already did that for us
    L5:
    orb $1, (%eax)          #if 1 then or it with the most significant byte to turn the lsb to a 1 without changing the whole byte
    L4:
    decl    %esi            #decrement our counter
    decl    %eax            #decrement eax to get the next byte (moving from most significant to least significant)
    shlb    $1, (%eax)      
    movl    $1, %ecx
    movl    $0, %edi
    testb   $0x80, -1(%eax)
    cmove   %edi, %ecx      #if the one before it is a 0 then let ecx equal zero other wise ecx is 1
    orb     %cl, (%eax) 
    cmpl    $1, %esi        #we don't want it to reach the last byte that would be done after
    jne     L4
    decl %eax               #get the last byte
    shlb    $1, (%eax)
    orb     %dl, (%eax)     #or it with the value obtained in line 26
    popl    %edi
    popl    %esi
    leave
    ret

最后输出:

before = 32 33 34 35
after  = 64 66 68 6a
before = 64 66 68 6a
after  = c8 cc d0 d4
before = c8 cc d0 d4
after  = 91 99 a1 a9
before = 91 99 a1 a9
after  = 23 33 43 53
before = 23 33 43 53
after  = 46 66 86 a6
before = 46 66 86 a6
after  = 8d cc 0c 4d
before = 8d cc 0c 4d
after  = 1a 99 19 9a
before = 1a 99 19 9a
after  = 35 32 33 34

对于那些对这个问题感到好奇的人,OP和我进行了讨论,似乎他的shell可能不喜欢stdin / stdout中的0x1a或0x19字符。在第8次迭代中,它拒绝阅读正确的输入&#34; 1a 99 19 9a&#34;。 0x1a的ascii含义是&#34;替换&#34;和0x19是&#34;媒体结束&#34;。所以也许&#34;媒体的结束&#34;关闭管道或东西。一切都很适合我(Win32上的cygwin bash)。

最后一件事是我有OP尝试&#34; 6789&#34;为了避免得到0x1a和0x19,他的程序运行得很好。这是另一个指标,表明他的设置中的某些内容并不像那些字节。

答案 1 :(得分:0)

已编辑:已修改回复,已理解原始代码。

您的代码所做的不是普通的轮换。它有效地将字符串中每个字节的最左边的位转移到下一个字节的最右端(在最后一个字节处包裹)。

所以,就像这样:

ab     c de     f gh     i   
00111011 10100010 11110000

 b     cg e     fa h     id
 01110111 01000100 11100001

       cgh      fab      ide   
  11101111 10001000 11000010

....

通常,这种准旋转不等于普通旋转。但是如果有8n个准旋转,则结果恰好相当于整个弦的8n标准右旋。

使用rclb指令可以非常有效地实现准旋转。

大会:

.text
.globl bitDrift

bitDrift:
    pushl %ebp
    movl %esp, %ebp
    movl 8(%ebp), %eax     # %eax holds the position of the current byte
    movl 12(%ebp), %ecx    # %ecx holds the number of bytes
    movl %eax, %edx        # %edx holds the position of the first byte
    clc                    # set the carry bit to zero
nextd:
    rclb $1, (%eax)        # left-rotate the current byte, putting the 
                           # existing carry bit in the vacant position and
                           # putting the bit rotated out into the carry bit
    incl %eax              # move to the next byte
    loop nextd             # decrement the count in %ecx and keep going 
                           # until it is 0
    adcb $0, (%edx)         # add the carry bit from the last rotation to 
                           # the first byte
    pop %ebp
    ret

C代码(你的代码也应该没问题;这只是我可以测试的代码):

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 2048

void bitDrift(char *, int);

int main(int argc, char **argv) {
    unsigned char buffer[BUFFER_SIZE];
    buffer[0] = 0x32;
    buffer[1] = 0x33;
    buffer[2] = 0x34;
    buffer[3] = 0x35;
    int i;
    for (i=0; i<33; i++) {
        printf("%02x %02x %02x %02x\n", buffer[0],
               buffer[1], buffer[2], buffer[3]);
        bitDrift(buffer, 4);
    }
}

使用gcc -g rot.c rot.S -o rot汇总。

输出:

32 33 34 35
64 66 68 6a
c8 cc d0 d4
91 99 a1 a9
23 33 43 53
46 66 86 a6
8d cc 0c 4d
1a 99 19 9a
35 32 33 34
6a 64 66 68
d4 c8 cc d0
a9 91 99 a1
53 23 33 43
a6 46 66 86
4d 8d cc 0c
9a 1a 99 19
34 35 32 33
68 6a 64 66
d0 d4 c8 cc
a1 a9 91 99
43 53 23 33
86 a6 46 66
0c 4d 8d cc
19 9a 1a 99
33 34 35 32
66 68 6a 64
cc d0 d4 c8
99 a1 a9 91
33 43 53 23
66 86 a6 46
cc 0c 4d 8d
99 19 9a 1a
32 33 34 35

此字符串中有32位,字符串在32次旋转后重复。