I'm surprised the author opted for guesswork and reverse-engineering GDB when it's of course open source. Admittedly GDB's source code can be a bit daunting but at least you'll have all the nasty details.
The relevant code appears to be in gdb/infcall.c in the `call_function_by_hand` method. It's pretty well commented too and shows that while the author gets the basics right there are many, many corner cases to consider to get it right all the time (threads, signals, exceptions, architecture and ABI quirks, etc...): https://github.com/rofl0r/gdb/blame/master/gdb/infcall.c#L46...
While I agree that reading source code is quite useful and informative, it really is no substitute for reverse engineering and inspecting on actual hardware.
It's quite easy to be mislead by source code, especially if you're trying to guess what output the compiler will actually generate.
As the author mentions elsewhere in this thread, both reading the source and reverse engineering (inspecting output, running under debugger, etc) are ideal.
Thanks for the link to `call_function_by_hand`, that's super helpful! In retrospect, I could have found this file by grepping for 'When the function is done executing, GDB will silently stop' (the very useful "grep for UI text" trick!)
the choice between whether to try to understand something by running or by reading the code is a super interesting one (though if I'm seriously interested in understanding exactly what some software does of course I'll do both!).
I think experimentation/stracing a program to understand it has a lot of advantages:
* It can be helpful to look at the system calls as a way to maybe see the big picture before diving into the code.("ok, it's setting some registers somehow, it's putting in some int3 instructions, then it does PTRACE_CONT, then it undoes all the changes it made").
* it lets me focus on what the program actually does instead of understanding how the code is organized (figuring out how the code in a large project I've never looked at before is organized can be time consuming!)
* It's easy to make incorrect conclusions when reading the source. This happens to me a lot when reading distributed systems code -- often I'll think "ok, if I do X, then Y will happen" but that turns out not to be true in practice. So I think even when you are reading code, doing experimentation as you go is a key part of making sure that your understanding of the code is actually correct.
* I find exploring programs interactively fun!
In this specific example -- I think it's really interesting that gdb modifies the `longjmp` function in libc when running a function. The string `longjmp` doesn't appear anywhere in infcall.c. So even though reading infcall.c seems like a great overview, it just has different information that I get from using strace!
I see where you're coming from, in the past I've gdb'd... gdb itself to figure out an obscure bug. It gets a bit confusing at times (better use different prompts at least) but this way you could step through the debugger's code itself instead of deciphering strace's output for instance.
Just, one thing: for C/C++ code-bases such as gdb, stop using grep for things, people! Use cscope! 'tis a brilliant means of navigating files and scoping out the contents of a project.
cscope is a sine qua non of C (and even C++ and Java) development. Of course, with OOP languages a dumb lexical indexer is not that useful (this applies to OpenGrok and many others too) -- if you're looking for calls to the getName() method of a particular class (and sub-classes) in a huge codebase then you'll be in for a lot of pain. But for C cscope is absolutely fantastic and much much better than etags/ctags.
I use cscope in conjunction w ctags. cscope (which will launch an editor, appropriately ) for a top-level overview/dive, and ctags (from w/i my vi instance, whether vi was launched from cscope or not) when I’m zeroing-in on a problem and can chase down thoughts and maintain my way back out because navigation is pushing/popping on a stack. No need for either/or; use them both.
I'm a BSD nvi user, so no vim for me. I do apparently have access to cscope database from w/i nvi, but my finger muscle memory wrt (ex)ctags support gets me a long way (perhaps I could do better w/ cscope? Something to look into.)
I'm not sure how to parse your last question -- rephrase ?
I find it easier to cut-n-paste (with tmux) the symbol I want to chase down, pasting it into cscope.
The reason this is better is that ctags doesn't do what I want. I may want to see functions called by some symbol (ctags no help), functions calling that symbol (two ctags operations required), or assignments to a variable (ctags no help) or even just a full-text search (again, ctags no help).
I don't need $EDITOR cscope integration because I have an easier time tracking appropriately-titled tmux windows than I do tracking $EDITOR buffers (not least because tmux windows are more general!), so I really want a new tmux window with a new $EDITOR instance. Thus the cut-n-paste approach, while it requires a few more keystrokes than I might like, works best. I could always do my own integration that uses tmux to programmatically write the symbol I want to the cscope window, but I don't feel the need.
I prefer cscope -Rqk. I run cscope on the zeroth window of a tmux/screen session with a $CSCOPE_EDITOR program that's a script that starts the real $EDITOR in a new window in the same session (with an appropriate window name) and in the background so that cscope gets control back immediately. This allows me to use cscope to drive large code investigations very easily.
Sounds cool. I use it in vim as a 'grok this C/C++/Lua/Textfiles' utility, pretty much every day. grep too of course, and there are times when you just wanna ack, but for full grok, cscope is cool however you do it.
I have a shell function that generates shell functions that do find | xargs ... patterns. Thus I have 'fsg' == find source grep -- that is, find with -name arguments matching source | xargs grep ..., and fseg (find source egrep) and fmg (find make stuff grep). These functions take all non-option looking arguments and use them as directory arguments for find, then all remaining arguments are passed to grep, so I get to write:
Cscope feels kind of lousy nowadays after I have been using opengrok. It handles multilang repos so much nicer and allows you to search all the codebases instead of just one.
longjmp() (and friends) is trapped because it causes a
non-local transfer of control. Basically, it can go
anywhere. the "p foo()" would then not return to the prompt.
What we know is that longjmp() will leave the function (it
goes to where the setjmp() was called from).
Also, look into libffi for how to call a C function by building a call.
GDB builds a dummy-frame for the inferior function call, and the unwinder cannot seek for exception handlers outside of this dummy-frame. What happens in that case is controlled by the set unwind-on-terminating-exception command.
Oh god, this brings me back (I used to be the C++ maintainer for GDB eons ago)
It's even more convoluted than the author suspects, because
1. GDB has to figure out which function to call (it does overload resolution for C++, for example, and this varies by language)
2. GDB has to do type coercion on the arguments if necessary (ditto)
3. There are significant ABI and other issues, both at the processor level, and at the language level, in where the arguments go (registers or memory), in calling the right thunks, etc.
This is one reason LLDB uses clang to do most of the work of telling it what the expression you are trying to call actually means, so it can just pretty much execute it.
The Delphi compiler used the constant expression folder to evaluate debugger expressions; it had hooks for converting symbol references into values, and for performing method calls, but a good chunk of the evaluation was interpreted that way. I thought it was a nifty reuse.
(Obviously, the expression is parsed, typed and bound using the compiler front end using the same symbols as the compiled code, so that bit is easy.)
Yeah, that works nicely if you can do it.
GDB unfortunately reimplements a hacked up C/C++ frontend.
(For some other languages, it doesn't have to because they have dynamic expression evaluation interfaces as part of their runtimes)
My day-job involves GDBing C code. I use a handful of functions regularly when I'm trying to pin down a bug, mostly just to double-check why the code is following a given branch. So I'm heavily dependent on memcmp, strncmp, etc.
I've noticed that those functions fail if I start trying to use them too early, so I end up having to add a "b main" first and wait until I get to the breakpoint before they become available.
Can someone explain why? I assume it has to do with getting far enough along in the program's execution that the shared libs have been loaded into memory?
If you mean you can't call them, yes, it's shared libs, they aren't in the address space yet.
If you mean something else (IE the symbol info is wrong), you can debug what is going on as follows:
The logic for when it builds minimal symbol tables, converting them to partial symbol tables, and then full symbol tables, is incredibly convoluted.
In theory, needing a full symbol with type info should just cause the right thing to happen lazily.
In practice, it does not.
You can debug what is going on by doing this:
(gdb) set debug symbol-lookup 1
(gdb) set debug symtab-create 1
(gdb) file <your executable>
(gdb) p strcmp("foo", "bar")
<look at output>
(gdb) b main
(gdb) p strcmp
<look at output>
This is going to produce a truly ridiculous amount of output on a large executable (so i'd use a minimal one :P), but by comparing the output you get trying to use the symbol before and after "b main" should give you some idea what is going on.
32 comments
[ 3.2 ms ] story [ 125 ms ] threadThe relevant code appears to be in gdb/infcall.c in the `call_function_by_hand` method. It's pretty well commented too and shows that while the author gets the basics right there are many, many corner cases to consider to get it right all the time (threads, signals, exceptions, architecture and ABI quirks, etc...): https://github.com/rofl0r/gdb/blame/master/gdb/infcall.c#L46...
Not because many people can't do it, but they simply don't do it.
It's quite easy to be mislead by source code, especially if you're trying to guess what output the compiler will actually generate.
As the author mentions elsewhere in this thread, both reading the source and reverse engineering (inspecting output, running under debugger, etc) are ideal.
the choice between whether to try to understand something by running or by reading the code is a super interesting one (though if I'm seriously interested in understanding exactly what some software does of course I'll do both!).
I think experimentation/stracing a program to understand it has a lot of advantages:
* It can be helpful to look at the system calls as a way to maybe see the big picture before diving into the code.("ok, it's setting some registers somehow, it's putting in some int3 instructions, then it does PTRACE_CONT, then it undoes all the changes it made").
* it lets me focus on what the program actually does instead of understanding how the code is organized (figuring out how the code in a large project I've never looked at before is organized can be time consuming!)
* It's easy to make incorrect conclusions when reading the source. This happens to me a lot when reading distributed systems code -- often I'll think "ok, if I do X, then Y will happen" but that turns out not to be true in practice. So I think even when you are reading code, doing experimentation as you go is a key part of making sure that your understanding of the code is actually correct.
* I find exploring programs interactively fun!
In this specific example -- I think it's really interesting that gdb modifies the `longjmp` function in libc when running a function. The string `longjmp` doesn't appear anywhere in infcall.c. So even though reading infcall.c seems like a great overview, it just has different information that I get from using strace!
Just, one thing: for C/C++ code-bases such as gdb, stop using grep for things, people! Use cscope! 'tis a brilliant means of navigating files and scoping out the contents of a project.
http://cscope.sourceforge.net
$ cd gdb/src; cscope -R
I'm not sure how to parse your last question -- rephrase ?
Anyway, I concur with your reasoning: bsd vi and muscle memory. Works for me!
The reason this is better is that ctags doesn't do what I want. I may want to see functions called by some symbol (ctags no help), functions calling that symbol (two ctags operations required), or assignments to a variable (ctags no help) or even just a full-text search (again, ctags no help).
I don't need $EDITOR cscope integration because I have an easier time tracking appropriately-titled tmux windows than I do tracking $EDITOR buffers (not least because tmux windows are more general!), so I really want a new tmux window with a new $EDITOR instance. Thus the cut-n-paste approach, while it requires a few more keystrokes than I might like, works best. I could always do my own integration that uses tmux to programmatically write the symbol I want to the cscope window, but I don't feel the need.
I prefer cscope -Rqk. I run cscope on the zeroth window of a tmux/screen session with a $CSCOPE_EDITOR program that's a script that starts the real $EDITOR in a new window in the same session (with an appropriate window name) and in the background so that cscope gets control back immediately. This allows me to use cscope to drive large code investigations very easily.
Also, look into libffi for how to call a C function by building a call.
It's even more convoluted than the author suspects, because
1. GDB has to figure out which function to call (it does overload resolution for C++, for example, and this varies by language)
2. GDB has to do type coercion on the arguments if necessary (ditto)
3. There are significant ABI and other issues, both at the processor level, and at the language level, in where the arguments go (registers or memory), in calling the right thunks, etc.
This is one reason LLDB uses clang to do most of the work of telling it what the expression you are trying to call actually means, so it can just pretty much execute it.
(Obviously, the expression is parsed, typed and bound using the compiler front end using the same symbols as the compiled code, so that bit is easy.)
My day-job involves GDBing C code. I use a handful of functions regularly when I'm trying to pin down a bug, mostly just to double-check why the code is following a given branch. So I'm heavily dependent on memcmp, strncmp, etc.
I've noticed that those functions fail if I start trying to use them too early, so I end up having to add a "b main" first and wait until I get to the breakpoint before they become available.
Can someone explain why? I assume it has to do with getting far enough along in the program's execution that the shared libs have been loaded into memory?
If you mean you can't call them, yes, it's shared libs, they aren't in the address space yet. If you mean something else (IE the symbol info is wrong), you can debug what is going on as follows:
The logic for when it builds minimal symbol tables, converting them to partial symbol tables, and then full symbol tables, is incredibly convoluted.
In theory, needing a full symbol with type info should just cause the right thing to happen lazily.
In practice, it does not.
You can debug what is going on by doing this:
This is going to produce a truly ridiculous amount of output on a large executable (so i'd use a minimal one :P), but by comparing the output you get trying to use the symbol before and after "b main" should give you some idea what is going on.