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

All off topic discussions go here. Everything from the funny thing your cat did to your favorite tv shows. Non-programming computer questions are ok too.
Post Reply
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

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

Post 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!
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

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

Post by iansjack »

Why would you expect a compiler to emit instructions for unreachable code?
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

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

Post 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.
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

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

Post 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.
User avatar
matt11235
Member
Member
Posts: 286
Joined: Tue Aug 02, 2016 1:52 pm
Location: East Riding of Yorkshire, UK

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

Post 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
com.sun.java.swing.plaf.nimbus.InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
Compiler Development Forum
Nable
Member
Member
Posts: 453
Joined: Tue Nov 08, 2011 11:35 am

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

Post 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
Korona
Member
Member
Posts: 1000
Joined: Thu May 17, 2007 1:27 pm
Contact:

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

Post 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.
managarm: Microkernel-based OS capable of running a Wayland desktop (Discord: https://discord.gg/7WB6Ur3). My OS-dev projects: [mlibc: Portable C library for managarm, qword, Linux, Sigma, ...] [LAI: AML interpreter] [xbstrap: Build system for OS distributions].
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

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

Post 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 :-)
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

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

Post 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!
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

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

Post 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
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

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

Post by iansjack »

And yet you talk about manually changing a register in gdb.
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

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

Post 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.
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

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

Post 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.
User avatar
xenos
Member
Member
Posts: 1121
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

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

Post 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.
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

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

Post 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.
Post Reply