1

I will be thankful for finding mistakes in my code. I was supposed to write a function that takes an address to a buffer, the letter l, the number n and the increase variable that can take only 2 values: 0 and 1. If the increase variable is 0, the function is supposed to repeat the same letter n times. If the increase variable is 1, the function should return a string of subsequent letters, for example "abcd...." (subsequent ascii characters). The letter l determines which letter we start with the string.

I tried using ddd, it tells me that the problem is with the line MOVL %ecx, (%edx) And I know that there is a wrong value in the register edx and ecx. Still, I cannot understand what is wrong and how to correct it. I will be very thankful for help.

#include <stdio.h>
#include <stdlib.h>
extern char * generate_str(char * s, int c, int n, int inc);

int main()
{
    char s[100] = "something";
    char c = 'a';
    int n = 5;
    int inc = 0;
    printf("String %s\n", generate_str(s, (int)c, n, inc));
}

Assembly code:

.data
character: .int 0

# char -> 1
# int -> 4
# arguments: char * s, int c, int n, int inc

.equ bufor,8
.equ c,12
.equ n,16
.equ inc,20
#eax, ebx, ecx, edx

.text
.type generate_str, @function
.global generate_str

generate_str:

    PUSHL %ebp            #prolog of the function
    MOVL %esp, %ebp
    MOVL inc(%esp), %eax  #copy variable inc into eax
    MOVL n(%esp), %ebx    #copy variable n into ebx
    PUSHL %ecx            #save contents of ecx
    MOVL c(%esp), %ecx    #copy variable c into ecx temporarily
    MOVL %ecx, character  #copy variable c into reserved memory called character
    POPL %ecx             #restore contents of c
    MOVL bufor(%esp), %edx #copy addres of a buffer into edx

    CMP $0, %eax # eax > 0 ?  #is inc variable 0 or 1
    JA one                    #if it is 1, go to line "one"
    MOVL %ebx, %ecx           %copy value of variable n into ecx, it tells how many letters should be placed in the buffer 
p:
    PUSHL %ecx                #save contents of ecx
    MOVL character, %ecx      #copy character into ecx
    MOVL %ecx, (%edx)         #copy character into the place in the memory which address is given in edx
    POPL %ecx                 #restore contents of ecx
    ADDL $4, %edx             #increase value of edx by 4, so we move forwards in the memory to save another letter there
    loop p                    #loop until ecx is 0
    jmp end                   #jump to the final part of the function

one:                          #if the value of inc is 1 then do another loop  
    PUSHL %ecx                #save ecx and use this register to copy character into the place in memory which address is in the edx registry
    MOVL character, %ecx
    MOVL %ecx, (%edx)
    POPL %ecx
    ADDL $1, character        #increase ascii character by 1
    ADDL $4, %edx             #move in memory by 4 bytes so we can save the next letter
    loop one                  #continue loop until ecx is zero
    jmp end

end:
    MOVL %edx, %eax          #copy address of the final string into eax
    movl %ebp,%esp           #restore registers
    popl %ebp
RET
Cœur
  • 37,241
  • 25
  • 195
  • 267
Joanna
  • 154
  • 2
  • 13
  • 1
    `MOVL bufor(%esp), %edx` maybe you meant `LEAL bufor(%esp), %edx`. Hard to tell, because you didn't comment your code properly. Also, since you can now apparently use `ddd`, single step the code and see where it does something wrong, don't just look at the faulting instruction. PS: `ebx` is a callee saved register. – Jester Jun 25 '16 at 15:21
  • I added comments to the code. How does one single step in ddd? Every time I press "step" it tells me that the program does not run. Every time I run the program it finishes before I can press "step". – Joanna Jun 25 '16 at 15:30
  • You put a breakpoint at the start of your function or wherever you want, then you can single step from there. Yeah, to `copy address` you need to use `lea` not `mov`. – Jester Jun 25 '16 at 15:41

1 Answers1

0

Thank you for your advice. DDD did its job. Here is the corrected code. To be honest I still have problems in understanding why I was supposed to add $1 instead of $4 when I was increasing the address under which I was supposed to insert the next char (since it was converted into int) but it works.

#include <stdio.h>
#include <stdlib.h>
extern char * generate_str(char * s, int c, int n, int inc);

int main()
{
    char s[100] = "cos tam";
    char c = 'a';
    int n = 5;
    int inc = 0;
    printf("String %s\n", (char*) generate_str(s, (int)c, n, inc));

}

Assembly code:

#dane
.data
character: .int 0

# char -> 1
# int -> 4
# arguments: char * s, int c, int n, int inc

.equ bufor,8
.equ c,12
.equ n,16
.equ inc,20
#eax, ebx, ecx, edx

.text
.type generate_str, @function
.global generate_str

generate_str:
    PUSHL %ebp
    MOVL %esp, %ebp
    MOVL inc(%esp), %eax 
    MOVL n(%esp), %ecx 
    MOVL c(%esp), %ebx
    MOVL bufor(%esp), %edx 
    PUSHL %ebx      

    CMP $0, %eax
    JA one
p:
    MOVL %ebx, (%edx)
    ADDL $1, %edx
    loop p
    ADDL $0, %edx
    jmp end

one:
    MOVL %ebx, (%edx)
    ADDL $1, %ebx
    ADDL $1, %edx
    loop one    
    ADDL $0, %edx
    jmp end

end:
    MOVL bufor(%esp), %edx
    MOVL %edx, %eax
    movl %ebp,%esp
    popl %ebx
    popl %ebp
    RET

And the version that uses a variable

#dane
.data
letter: .int 0

# char -> 1
# int -> 4
# arguments: char * s, int c, int n, int inc

.equ bufor,8
.equ c,12
.equ n,16
.equ inc,20
#eax, ebx, ecx, edx

.text
.type generate_str, @function
.global generate_str

generate_str:
     PUSHL %ebp
     MOVL %esp, %ebp
     MOVL inc(%esp), %eax 
     MOVL n(%esp), %ecx 
     MOVL c(%esp), letter
     MOVL bufor(%esp), %edx 
     PUSHL %ebx 

     CMP $0, %eax
     JA one
 p:
     MOVL letter, %edi
     MOVL %edi, (%edx)
     ADDL $1, %edx
     loop p
     jmp end

one:
     MOVL %ebx, (%edx)
     ADDL $1, %ebx
     ADDL $1, %edx
     loop one   
     ADDL $0, %edx
     jmp end

end:
      MOVL bufor(%esp), %edx
      MOVL %edx, %eax
      MOVL %ebp,%esp
      popl %ebx
      popl %ebp
      RET
Joanna
  • 154
  • 2
  • 13
  • *understanding why I was supposed to add $1 instead of $4 when I was increasing the address under which I was supposed to insert the next char (since it was converted into int)*. A string is an array of bytes. In memory, each element is one byte. Sure you can convert on the fly to an int, but *in memory*, each `char` is one byte. – Peter Cordes Jun 25 '16 at 19:55
  • It looks like you're still destroying the caller's `%ebx`, so your code will crash if used from a more complicated calling function, and/or if compiled with optimization enabled. See http://stackoverflow.com/questions/8335582/why-does-ia-32-have-a-non-intuitive-caller-and-callee-register-saving-convention, and other stuff about ABIs in [the x86 tag wiki](http://stackoverflow.com/tags/x86/info) – Peter Cordes Jun 25 '16 at 19:57
  • I added PUSHL %ebx and POPL %ebx. How about now? – Joanna Jun 25 '16 at 21:06
  • I think you caused another bug when you fixed the `%ebx` bug, because you didn't change anything else to account for the extra 4 bytes you push onto the stack. e.g. you didn't change the offsets for `MOVL n(%esp), %ecx`. Normally you'd keep the `push %ebp` first, and reference args on the stack relative to it. (The offset to any given arg from `%ebp` is a constant for the whole function, unlike the offset to `%esp`. This is the main benefit of making a "stack frame" with ebp: you don't have to keep track of where `%esp` is pointing to get at your args and locals.) – Peter Cordes Jun 25 '16 at 21:11
  • Look at some compiler output for a small function (from `gcc -m32 -fno-omit-frame-pointer -O3`) for an example. e.g. [this example on the Godbolt compiler explorer](https://godbolt.org/g/81Itrz) demonstrates making a stack frame and then saving %ebx, and accessing a function arg relative to %ebp. Clang's version is strangely non-optimal (using `push %eax` to reserve space / align the stack, and then `mov %eax, (%esp)` after calculating a value.) gcc's version has an interesting way of cleaning up the stack after the call, with a `mov` load of ebx, and then a `leave` instead of `pop %ebp`. – Peter Cordes Jun 25 '16 at 21:27
  • Anyway, knowing the right terminology (like "stack frame", "calling convention", "ABI") gives you something to search on for more details about this stuff. – Peter Cordes Jun 25 '16 at 21:29
  • You need to also swap the order of the `pop` instructions after changing the order you `push`.. Your last change swaps the caller's ebx with ebp. – Peter Cordes Jun 26 '16 at 16:14
  • Did you even try this code before editing it into what you're claiming is a useful answer to the question? `MOVL %ebp,%esp` before `popl %ebx` means you're reading from the wrong place. I assume you'll just crash on the `ret`, because `%esp` won't be pointing at the return address when it runs. `MOVL c(%esp), letter` won't even assemble, and you need to save/restore `%esi` and `%edi` if you use them. – Peter Cordes Jun 26 '16 at 21:01
  • Just to be clear, I'm not saying this is easy or obvious, I'm just saying that making edits to an answer isn't the right place to put code that doesn't work. I keep an eye on the x86 tag (among others), so I keep noticing this question in the recently-active front page and coming here to see edits that introduce new bugs. I'd be happier if you just left this wrong answer alone for a while until you have it sorted out, then come and edit in something that you think actually works. Happy debugging :) Notify me with a comment (@peter) once you have something you think is good. – Peter Cordes Jun 26 '16 at 21:04