3

I'm debugging a big multi-threaded application and trying to catch a bug when an invalid pointer value is being assigned to a variable. I cannot simply backtrace from the point of failure because consumer(s) and producer(s) are different threads, so I'm trying to set up a conditional breakpoint on every assignment statement of that variable to test if assigned value is invalid.

Let's consider this example:

#include <stdio.h>

int main() {
    char* arr[] = {"foo", "bar", 0x1234, "baz"};

    for(int i=0; i<4; ++i) {
        char* str = arr[i];

        puts(str);
    }
}

I found that I can handle it like this:

break test.c:7 if !arr[i][0]

It works, however, I'm unsure if this approach is anyhow reliable because it actually tries to access (probably) invalid memory addr. Also, it has an obvious false-positive when string is empty.

Also, I came across this answer, but it looks like I'd have to somehow tell linker to add those functions into my executable to be available for gdb, and also it looks too complicated to use inside a one-liner condition.

So, my question is: is there any reliable way to check if pointer is invalid inside gdb, which is short enough to use inside a one-liner condition and does not depend on pointed value.

Denis Sheremet
  • 2,453
  • 2
  • 18
  • 34
  • 1
    "I'm unsure if this approach is anyhow reliable". I can't see how that is reliable even apart from the issues you have pointed out. The condition checks for a 0 value but there is no guarantees at all that an invalid pointer points to 0. In fact, an invalid pointer is likely to either seg fault when accessed or even worse for you, it will point to random memory which will not seg fault and will not return a predictable value when accessed. – kaylum Dec 12 '19 at 05:27
  • So stepping back a bit, what is your definition of "invalid pointer" in this case? That is, how are you coming to the conclusion that the problem is that a pointer has an invalid value? – kaylum Dec 12 '19 at 05:30
  • @kaylum here by "invalid pointer" I mean "something that will lead to segfault when dereferenced". I am aware that this definition is quite too narrow, but for my case it's enough – Denis Sheremet Dec 12 '19 at 05:48
  • It seems you know where the assignment happens. If that is true then just change the assignment to be a function call. The function call will first try to dereference the pointer and then do the actual assignment. The debugger can then catch this as a normal seg fault. Be careful to ensure that the compiler does not optimise out the dereference part of the code. – kaylum Dec 12 '19 at 05:53
  • Thanks for advice, but this is some legacy code and I don't really want to modify it unless necessary. I will do so though if there's no better approach – Denis Sheremet Dec 12 '19 at 05:57

1 Answers1

3

Since you are on Linux, the best approach is most likely to use reverse debugger, such as rr.

You run the program with rr record ./a.out arg0 ..., and then, once it crashes, you can set a watch point on the "bad" variable, and reverse-continue right to the place where the variable was last changed. It works like magic!

Here is a session with your example:

$ rr record ./a.out
rr: Saving execution to trace directory `$HOME/.local/share/rr/a.out-5'.
foo
bar
Segmentation fault

$ rr replay
...
Program stopped.
0x00007f69546b4f30 in _start () from /lib64/ld-linux-x86-64.so.2
(rr) c
Continuing.
foo
bar

Program received signal SIGSEGV, Segmentation fault.
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:96
96  ../sysdeps/x86_64/multiarch/strlen-avx2.S: No such file or directory.
(rr) bt
#0  __strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:96
#1  0x00007f6954366040 in __GI__IO_puts (str=0x1234 <error: Cannot access memory at address 0x1234>) at ioputs.c:35
#2  0x000056227d5ec189 in main () at foo.c:9

(rr) up
#1  0x00007f6954366040 in __GI__IO_puts (str=0x1234 <error: Cannot access memory at address 0x1234>) at ioputs.c:35
35  ioputs.c: No such file or directory.
(rr) up
#2  0x000056227d5ec189 in main () at foo.c:9
9           puts(str);
(rr) p str
$1 = 0x1234 <error: Cannot access memory at address 0x1234>
(rr) watch -l str
Hardware watchpoint 1: -location str
(rr) reverse-cont
Continuing.

Program received signal SIGSEGV, Segmentation fault.
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:96
96  ../sysdeps/x86_64/multiarch/strlen-avx2.S: No such file or directory.
(rr) reverse-cont
Continuing.

Hardware watchpoint 1: -location str

Old value = 0x1234 <error: Cannot access memory at address 0x1234>
New value = 0x56227d5ed008 "bar"
0x000056227d5ec179 in main () at foo.c:7
7           char* str = arr[i];
(rr) p i
$2 = 2
(rr) p arr[i]
$3 = 0x1234 <error: Cannot access memory at address 0x1234>

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • I was thinking of that but not sure whether it will work well with multiple threads. Especially if the assignment is in one thread and the pointer deref is in another as seems to be the case here. What do you think? – kaylum Dec 12 '19 at 06:15
  • Great! Then that is definetely the way to go. – kaylum Dec 12 '19 at 06:17
  • Looks nice. Did not know about that tool, definetely going to take a look – Denis Sheremet Dec 12 '19 at 07:05