Page 1 of 1

Another happy day in the wonderful world of GNU toolchain...

Posted: Sat Jun 24, 2017 1:24 pm
by bzt
Okay, here's the thing I sucked with hard. I share the solution in case somebody will have the same problem.

I wanted to set up a breakpoint in gdb, and since it can't handle the symbols correctly, I decided to use the "jmp $" and "set $pc+=2" trick to step through. My code is as follows:

Code: Select all

    /*** endless loop ***/
dbg_printf("dispatch loop\n");
ee: goto ee;
    while(1) {
        /* get work */
        msg = mq_recv();

Code: Select all

(gdb) symbol-file bin/initrd/lib/libc.so 
Reading symbols from bin/initrd/lib/libc.so...done.
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x0000000000209b02 in ?? ()
(gdb) x /16i $pc
=> 0x209b02:	jmp    0x209b02
   0x209b04:	leaveq 
   0x209b05:	retq   
   0x209b06:	push   %rbp
   0x209b07:	mov    %rsp,%rbp
That disassembly made me wonder, how on earth is this possible?

It shouldn't been a leaveq+retq there! So I've investigated a bit. I'm compiling with "-g -O0" flags, that should disable all optimizations. When I comment out the "ee: goto ee;", I got:

Code: Select all

objdump -d libc.so
...
    1b0e:       e8 dd 34 00 00          callq  4ff0 <dbg_printf@plt>
    1b13:       b8 00 00 00 00          mov    $0x0,%eax
    1b18:       e8 63 34 00 00          callq  4f80 <mq_recv@plt>
...
Which is fine. Now if I put that goto back there, it compiles to:

Code: Select all

objdump -d libc.so
...
    1b0b:       e8 00 33 00 00          callq  4e10 <dbg_printf@plt>
    1b10:       eb fe                   jmp    1b10 <mq_dispatch+0x1bc>
    1b12:       c9                      leaveq 
    1b13:       c3                      retq   

0000000000001b14 <atexit>:
Meaning the entire code I wanted to step through gone for good! <angry> Man, I hate software so much that thinks it's smarter than a man, especially when there's no way to tell it "no, you are dumb as hell, and I have already told you not to override me, so just do as I say!" </angry>

Solution, ughly it is, but works (at least on x86_64):

Code: Select all

    /*** endless loop ***/
dbg_printf("dispatch loop\n");
__asm__ __volatile__ ("1:jmp 1b");
    while(1) {
        /* get work */
        msg = mq_recv();

Code: Select all

objdump -d libc.so
...
    1b0e:       e8 ed 34 00 00          callq  5000 <dbg_printf@plt>
    1b13:       eb fe                   jmp    1b13 <mq_dispatch+0x1bf>
    1b15:       b8 00 00 00 00          mov    $0x0,%eax
    1b1a:       e8 71 34 00 00          callq  4f90 <mq_recv@plt>
...
Hurray!

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sat Jun 24, 2017 1:56 pm
by iansjack
Why would you expect a compiler to emit instructions for unreachable code?

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sat Jun 24, 2017 4:24 pm
by bzt
iansjack wrote:Why would you expect a compiler to emit instructions for unreachable code?
Because I've explicitly told the compiler not to optimize. Just because it "thinks" the code is unreachable, doesn't mean it is unnecessary.

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sat Jun 24, 2017 11:09 pm
by iansjack
1. You haven't told the compiler not to optimize. You need to read the GCC manual.

2. Unreachable code isn't unnecessary? It's logic, but not as we know it Jim.

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sun Jun 25, 2017 5:07 am
by matt11235
bzt wrote:Because I've explicitly told the compiler not to optimize.
There's still quite a few optimisations enabled when you pass -O0.

Code: Select all

$ gcc --version
gcc (GCC) 7.1.1 20170526 (Red Hat 7.1.1-2)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc -Q --help=optimizer -O0 | grep enabled | wc -l
56

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sun Jun 25, 2017 8:52 am
by Nable
Don't fight against your tools, just learn how to use them properly. If you don't care about portability, asm volatile ("int3") is what you need instead. Better solution is to use something like this snippet or this header

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sun Jun 25, 2017 8:54 am
by Korona
There are probably some passes that need to be done in order for GCC's code generators to work. Seems like the dead-code elimination pass is one of time. Things like SSA generation might need accurate control flow graphs.

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sun Jun 25, 2017 1:37 pm
by bzt
@iansjack: well, when you're debugging, everything is necessary. Btw, from the manual,

Code: Select all

-O0
Reduce compilation time and make debugging produce the expected results. This is the default.
I expect no code elimination when I debug :-) Besides, there's no way to tell gcc not to do so, and it's not just me, there are other programmers:
https://stackoverflow.com/questions/285 ... -dead-code

@matt11235: yep, you're right.

@Nable: I can't. I was already hacking computers by the time gcc had it's first beta release... :-) Your links are interesting, that's exactly what I do too https://github.com/bztsrc/osz/blob/mast ... form.h#L31. But the problem with int 3 is that it runs INSIDE the vm, and does not stop execution or instruct gdb for a debug prompt (instead invokes my internal debugger). I didn't wanted to run my debugger because it's also running inside the vm, and I wanted to observe from a distance without any possible interference. Since the problem only appeared when I booted from EFI, I couldn't use bochs either (I can only use TianoCore with qemu, it's not working with bochs for some reason).

@Korona: you're right. I've learned that there's a portable way, called asm goto. On the stackoverflow link above they also mentioning it with branch prediction.

Anyway, it turned out that the problem was caused by a bug in my pmm, when EFI gave a memory map that was interpreted badly and at a certain point pmm_alloc() gave 0xFF000 instead of 9F000 to the vmm. Unluckily for me it did that when my init process' stack was allocated. Therefore init tried to save return pointer in the ROM... Needless to say it didn't worked. It was a really nasty bug and hard to trace, but now I've fixed it :-)

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sun Jun 25, 2017 2:44 pm
by iansjack
No.

If you are going to manually change registers then you can't expect the compiler to generate code that will allow for that possibility. Has it generated code to deal with the possibility of you changing the stack pointer to an invalid value (for example) - something that you might also decide to do manually in gdb? It's reasonable for the compiler to expect you to at least provide a hint that you are going to do something funny. Inserting the "jmp ." explicitly does this. (Well, at least you can't expect the compiler to take manually inserted instructions into account when optimizing code.)

You say there is no way to tell gcc not to discard the unreachable code, and yet you have demonstrated exactly how to tell it!

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sun Jun 25, 2017 4:36 pm
by bzt
@iansjack: I wasn't talking about changing registers and other low level stuff. I just wanted to use a high level "stop execution" without any side-effects. Also I've meant there's no command line option to force that, or at least it doesn't do what you expect it to do. What I've shown with asm("1:jmp 1b") is not a platform independent way, so it's not a 100% equivalent solution. Frankly I've never thought there's such a brainf*cked thing in gcc like asm goto, which is an oxymoron by itself :-) But it works, avoids code removal and it is portable :-D

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Sun Jun 25, 2017 11:46 pm
by iansjack
And yet you talk about manually changing a register in gdb.

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Mon Jun 26, 2017 8:51 am
by bzt
iansjack wrote:And yet you talk about manually changing a register in gdb.
I see you have a problem with understanding the concept, so let's make this clear.

There are two "instructions":

1. at source level, a platform independent, portable way of saying "stop execution". This should not involve any registers or any architecture specific stuff as it's in the C source. If it's capable of invoking the debugger prompt it's the best, but at least it must suspend all activities until the user enters remote debugger prompt manually. It's okay if it's a macro that specified differently for each architecture, but the source must be the same for all platforms. At a first glance I've thought "label:goto label;" fulfills those requirements without the need to specify it differently for every platform.

2. at debugger level, we need to say "continue execution". As it's at debugger level, it clearly cannot be platform independent, as debugging is always tied to a specific architecture you running your code on. But it's semantic is the same for all architectures: move the instruction pointer (or program counter) to the next instruction. after the one that caused the stop.

Sidenotes: if you use int $1 for "stop", the instruction pointer is already set up at the next instruction, so you don't need to adjust it manually. If you use some hardware assistance for stopping (like single stepping in x86), you don't have to adjust it either, as IP is already pointing at the next instruction. With other words the pre-requirement of continuing is already fulfilled, no actions needed. Now using hardware assistance for stopping requires at least macros, as it clearly cannot be described in C.

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Mon Jun 26, 2017 9:05 am
by iansjack
You are ignoring the fact that the instruction pointer is a register. By changing it manually you are changing the program in a way that the compiler can't predict. It's no different, in essence, from manually changing the stack pointer, CR3, or any other register.

I have no problems with the concept; I am quite familiar with using gdb to debug an os. And I know how to use breakpoints.

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Mon Jun 26, 2017 10:00 am
by xenos
This might be a too simple solution, and it might have some problems because of which it might not work, but this is what comes to my mind if I wanted to have a "closed lift gate" that my code cannot pass unless the debugger opens it:

Code: Select all

volatile bool lift_gate = false;

...

while(!lift_gate) ;

...
This should stop execution (or rather run into an endless loop, since you never change lift_gate in the code), but still tell the compiler that the code might continue after this point, when lift_gate gets changed externally. So in your debugger you can just flip the variable and your code continues. And this is perfectly portable.

Of course, setting a breakpoint in the debugger before you start execution is another option, unless you want to avoid this for some reason. All you need here is to figure out where exactly to set the breakpoint.

Re: Another happy day in the wonderful world of GNU toolchai

Posted: Mon Jun 26, 2017 10:40 am
by iansjack
I'd agree that setting a breakpoint is preferable to altering the code. I can't see any reason not to take this approach. Or, you could even set a watchpoint on a variable that you know is going to change.