0

Taking the following C code

#include <stdio.h>

void test(unsigned char buffer[], int size) {
    for (int i = 0; i < size; i++) {
        unsigned char data = buffer[i];
        printf("%c", data);
    }
}

void main() {
    unsigned char buffer[5] = "Hello";
    test(buffer, 5);
    return;
}

and compiling it the flags -fno-stack-protector -fno-asynchronous-unwind-tables -fno-unroll-loops for clarity produces the following assembly for the test() function:

test:
    testl   %esi, %esi
    jle .L6
    pushq   %rbp
    leal    -1(%rsi), %eax
    pushq   %rbx
    leaq    1(%rdi,%rax), %rbp
    movq    %rdi, %rbx
    subq    $8, %rsp
    .p2align 4,,10
    .p2align 3
.L3:
    movzbl  (%rbx), %edi
    addq    $1, %rbx
    call    putchar@PLT
    cmpq    %rbp, %rbx
    jne .L3
    addq    $8, %rsp
    popq    %rbx
    popq    %rbp
    ret
    .p2align 4,,10
    .p2align 3
.L6:
    ret
    .size   test, .-test
    .section    .text.startup,"ax",@progbits
    .p2align 4

It seems to me like the L3 label here is completely useless since it is never jumped to or entered. (Except by jne .L3, but that instruction is inside of the L3 label already).

Can anyone explain how and why this assembly still produces the expected effect?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 8
    `jne .L3` seems to jump to it. – Jester Jun 14 '21 at 11:09
  • 1
    You might have found that location via simple text search functionality of your favourite editor... – Aconcagua Jun 14 '21 at 11:11
  • `void main()`? I'm surprised you got that through the compiler at all. Are you compiling with or without optimization? Which version of gcc are you using? – Ted Lyngmo Jun 14 '21 at 11:11
  • @TedLyngmo Depends, maybe he's on a bare micro controller ;) – Aconcagua Jun 14 '21 at 11:12
  • the `jne .L3` is an instruction inside of the `L3` label though? – Davin Miler Jun 14 '21 at 11:13
  • @Aconcagua Yeah - but it's gcc so I thought it would refuse that - but it doesn't I now noticed when I tried it myself. – Ted Lyngmo Jun 14 '21 at 11:14
  • 4
    There is no "inside" to labels. They are just bookmarks. Also the cpu doesn't see them. You might be thinking the cpu stops before going through the `.L3`, it doesn't. – Jester Jun 14 '21 at 11:14
  • The code in L3 is entered from the code before it by continuing execution top to bottom... That p2align thing between produces a bunch of NOPs. –  Jun 14 '21 at 11:15
  • 1
    @TedLyngmo On my system: `../src/main.cpp:23:33: error: ‘::main’ must return ‘int’` – Aconcagua Jun 14 '21 at 11:17
  • @dratenik oh that makes sense. I misunderstood the idea of labels. Thanks a lot! – Davin Miler Jun 14 '21 at 11:18
  • @Aconcagua Yes, but this is in C mode and there it's a lot more flexible it seems [demo](https://godbolt.org/z/vsnGnr36e) – Ted Lyngmo Jun 14 '21 at 11:18
  • @TedLyngmo Right, have been in the wrong file... `../src/main.c:28:6: warning: return type of ‘main’ is not ‘int’ [-Wmain]` (`-Wall` enabled, not even pedantic). – Aconcagua Jun 14 '21 at 11:21
  • 1
    @Aconcagua Yes, OP should also add those. I recommend at least `-O3 -Wall -Wextra -pedantic -pedantic-errors` – Ted Lyngmo Jun 14 '21 at 11:23
  • 1
    @TedLyngmo I always add `-Werror=implicit`, too. – Aconcagua Jun 14 '21 at 11:25
  • No clue what seems to be the issue. The exact command i use to compile it is `gcc test.c -o test.out -O5 -fno-stack-protector -fno-asynchronous-unwind-tables -fno-unroll-loops` which runs without error or warning on my linux box. As for the what Aconcagua said before, yes i'm writing some custom assembly for a microcontroller. – Davin Miler Jun 14 '21 at 11:28
  • @DavinMiler I'd make use of the support for getting warnings for common mistakes no matter what the target platform is. `-Wall -Wextra` is the minimum I'd use to not let silly mistakes through which can ruin everything :) – Ted Lyngmo Jun 14 '21 at 13:50
  • There is no real issue. ISO C allows `void main` for freestanding programs, and GCC doesn't complain about it for even for normal programs. You compiled without `-ffreestanding`, but GCC (unlike g++) only warns about that with `-Wall` enabled. https://godbolt.org/z/aMfhsPM47. Still, you *should* compile with `-Wall` before posting code on SO, especially to ask questions about it, because *actual* weird compiler output (in other cases) may be the result of UB, or your C not meaning what you thought it meant, etc. And to avoid triggering OCD / distracting people with unrelated stuff like this. – Peter Cordes Jun 14 '21 at 15:36

1 Answers1

2

If you read the assembler code from the top you will see that it reaches .L3, plus it also jumps to it with jne .L3, which is your for loop in C.

the busybee
  • 10,755
  • 3
  • 13
  • 30
Brecht Sanders
  • 6,215
  • 1
  • 16
  • 40
  • Yup, this is correct. For some reason i thought that execution would halt when reaching a label, which is obviously incorrect. I'll mark the post as solved. – Davin Miler Jun 14 '21 at 11:22
  • @DavinMiler: [Why are loops always compiled into "do...while" style (tail jump)?](https://stackoverflow.com/q/47783926) covers the general case of loops in asm compiling this way. [Code executes condition wrong?](https://stackoverflow.com/q/32872539) and [What if there is no return statement in a CALLed block of code in assembly programs](https://stackoverflow.com/q/41205054) explain that labels are just symbolic names that let you refer to an address from elsewhere, nothing more, nothing less. – Peter Cordes Jun 14 '21 at 11:23