0

Im trying to concatenate two given strings using inline assembly in C using the gcc compiler and intel syntax. I have been successfull to print the first string but not the second and I can't figure out why. Here is my code:

#include <stdio.h>

void my_strcat(char *str1, const char *str2) {
    __asm__ volatile (
            "mov %[str1], %%rax\n\t"    // Move str1 pointer to rax register
            "mov %[str2], %%rbx\n\t"    // Move str2 pointer to rbx register
            "1:\n\t"                     // Loop label
            "cmpb $0, (%%rax)\n\t"       // Compare the value at rax with 0
            "jne 2f\n\t"                  // Jump to 2 if not equal (i.e., not end of str1)
            "lodsb\n\t"                  // Load a byte from str2 to al and increment str2 pointer
            "stosb\n\t"                  // Store a byte from al to str1 and increment str1 pointer
            "testb %%al, %%al\n\t"       // Test if al is 0
            "jne 1b\n\t"                  // Jump to 1 if not equal (i.e., not end of str2)
            "2:\n\t"                      // Loop label
            "dec %%rax\n\t"              // Decrement rax to undo the null terminator
            "movb $0, (%%rax)\n\t"       // Null terminate the concatenated string
            : [str1] "+r" (str1), [str2] "+r" (str2)   // Input/output operands
    :                                      // No input-only operands
    : "rax", "rbx", "memory"                // Clobbered registers and memory
    );
}

int main() {
    char str1[20] = "Hello";
    char str2[20] = " world!";
    my_strcat(str1, str2);
    printf("Concatenated string: %s\n", str1);
    return 0;
}

Any help would be appreciated !

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • What did you discover when you stepped through the code in a debugger? – Raymond Chen Apr 18 '23 at 20:19
  • In my main function, the strings hello and world are correctly passed onto the concatenate function, but when it receives string1, the concatenated string "str1" is just made of "hello". I imagine the world either never gets pushed or is deleted but i can't see it. The debugger doesn't let me put breakpoints on the inline assembly – Sebastien Fnt Apr 18 '23 at 20:24
  • 3
    Check the manual on `lodsb` and `stosb`. The instructions do not do what you expect them to do. I also strongly recommend against using inline assembly for this sort of stuff. Write regular assembly in an assembly file and link it into your program. – fuz Apr 18 '23 at 20:32
  • AL is the low byte of RAX. Check the manual for `lodsb` to see what register it actually uses for the pointer (https://www.felixcloutier.com/x86/stos:stosb:stosw:stosd:stosq), and use constraints like `"+D"(str1)` (https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html) to ask for the pointers in the correct registers (don't start your asm template with a `mov` register copy unless you need a 2nd copy of it.) – Peter Cordes Apr 18 '23 at 23:42
  • 1
    Also, you've implemented `strcpy` not `strcat`. If you want to use slow "string" instructions at all, use `repne scasb` with AL=0 to skip through the existing string at the destination. Your copy loop already copies the terminating zero, so you don't need to separately store one with `movb $0, -1(%rdi)` or whatever. – Peter Cordes Apr 18 '23 at 23:42
  • 1
    *The debugger doesn't let me put breakpoints on the inline assembly* - Then you're using it wrong, or your debugger is useless for what you're doing. In GDB, use `stepi` (aka `si`) to single-step by instruction. Use `layout reg`+ `layout next`), or `layout asm`, to get a disassembly view instead of source view (optionally along with register values that update automatically as you step, highlighting change). You can use `b` to set a breakpoint at the current instruction, or copy/paste an address from the disassembly view and e.g. `b *0x55555abc` to set a breakpoint at any numeric address. – Peter Cordes Apr 18 '23 at 23:45
  • See the bottom of https://stackoverflow.com/tags/x86/info for GDB asm tips – Peter Cordes Apr 18 '23 at 23:46
  • BTW, the reason this didn't just crash when using the wrong registers is that the x86-64 SysV calling convention passes the first 2 args in RDI and RSI, exactly where you want them for lods/stos. (That's not a coincidence; I think GCC used to inline `memcpy` as `rep movsb` or something in about 2000 when the calling convention was designed. [What's the best way to remember the x86-64 System V arg register order?](https://stackoverflow.com/q/63891991)) So RDI and RSI probably already held the right pointers in a debug build when entering your asm template. – Peter Cordes Apr 18 '23 at 23:50
  • Sebastien Fnt, Note that `strcat()` returns `char *`, unlike `void my_strcat(char *str1, const char *str2)`. Sure would be nice to have a `char * my_strcat(size_t destination_size, char *str1, const char *str2)`. – chux - Reinstate Monica Apr 19 '23 at 01:22
  • How about reverse engineering? Write some C code for concatenating strings, compile it to assembly, and then check the generated assembly to see how it is implemented by your compiler. – Farzam Apr 19 '23 at 22:42

0 Answers0