3

Suppose I have some C program like this:

#include <stdlib.h>
#include <stdbool.h>

int main()
{
    while (true) {
        void *p = malloc(1000);
        free(p);
    }
    return 0;
}

and I attach to it with gdb like this gdb a.out PID. gdb successfully attaches to it but that I try to do something like call printf("bla bla bla") gdb freezes and if I press Ctrl^C I get this:

(gdb) call printf("bla bla bla")
^C
Program received signal SIGINT, Interrupt.
__lll_lock_wait_private () at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:95
95  ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on".
Evaluation of the expression containing the function
(malloc) will be abandoned.
When the function is done executing, GDB will silently stop.

I suppose that this happens because my a.out was creating an object and acquired a lock inside malloc.c and in this moment I connected with gdb and tried to create string "bla bla bla" using malloc.

My question is how can I detect that I'm inside malloc.c and let my program finish this execution? I need to do it not inside command line but using some sort of gdb scripting (I only can execute commands inside gdb with -ex option).

PepeHands
  • 1,368
  • 5
  • 20
  • 36
  • This works for me... `printf`completed. I tried both running `gdb ./sample.out` and attaching to a running process. – Ishay Peled Aug 24 '16 at 13:42
  • @IshayPeled I think you just were lucky and `sample.out` just wasn't executing `malloc` at the moment. You can try to repeat it multiple times. – PepeHands Aug 24 '16 at 13:44
  • Calling functions from gdb is a dubious practice, especially from the standard library. One reason is what you just experienced. When debugging you want your program to be in a static state so that you can inspect it, calling functions that potentially change state is pretty counter-productive for debugging. I'd suggest finding a different way of solving your problem. – Art Aug 24 '16 at 13:51
  • @Art but as far as I know calling some functions inside `gdb` is the only way in which I can inject some code inside running process. Do you know any better ways? – PepeHands Aug 24 '16 at 13:55
  • @Dima right - it took me 10 attempts, but I eventually got hung. See my answer for a possible WA – Ishay Peled Aug 24 '16 at 13:56
  • @Dima Eh. Sure. There are many ways. `dlopen` for example. Suspending a process in an unpredictable state to "inject code" sounds like a sure way of shooting your own foot. If you want self-modifying code there are many languages that do it nicely, C is probably the worst choice. Btw. All kinds of alarm bells are going off in my head, I'm not sure I should have mentioned `dlopen` up there. What is the actual problem you're solving? – Art Aug 24 '16 at 14:00
  • @Art I do some `dlopen` inside my `gdb` process, but it seems to me that I will face same problems because `dlopen("some_constant_string", some_flags)` needs to create this string and will call `malloc` (if I understand that process right). My actual problem -- I need to connect to ruby process and create some threads there. – PepeHands Aug 24 '16 at 14:03
  • I meant `dlopen` as a well-designed part of your program you're "injecting code" into, not `dlopen` after shanking some unsuspecting process with gdb. And what you said is not an "actual problem", it's just the solution you decided to implement for some reason. Really, trust me, suspending a process with gdb to make it run some random code you inject has microscopic probability of ever working reliably. As an experiment, go for it, try a few times until it looks like it's working, but forget to ever make it reliable which it seems that you're aiming for. – Art Aug 24 '16 at 14:08

2 Answers2

3

The reason you're froze is probably a lock that's being held by your program, and is also required by printf. When you try to aquire it twice - you fail.

A possible WA is when breaking your program to call printf, just before you make the call, type finish - it will cause the current function to complete and return to the main frame. This will ensure the lock is free before you call printf.

Ishay Peled
  • 2,783
  • 1
  • 23
  • 37
  • yes, but the main problem here is how can I understand that I'm inside `malloc` and that I need to call finish? I'm running this `gdb` as subprocess inside another program so I don't have access to console and need to understand that executing commands with `-ex` (or I can write some `.gdbinit` script and define my function there). – PepeHands Aug 24 '16 at 13:59
  • Well, if the interesting code is in the outer loop (main function) as you describe here, that is not a problem - finish is ignored for outer loops. – Ishay Peled Aug 24 '16 at 14:01
  • Cool, I didn't know that, thank). But the problem still remains because my actual project is a bit more complicated (actually it is a ruby interpreter :)) and it can be inside some "infinite" loop which is not main function (I used quotes because it is running while my process is running). – PepeHands Aug 24 '16 at 14:06
  • Also I'm not sure, but if I really was inside `malloc` function and called `finish` does it mean that after that _all_ the threads are not inside this function? – PepeHands Aug 24 '16 at 14:09
  • You should check if you have other threads running around (there shouldn't be unless you created them) and finish each one. This should be scripted because it gets complicated... Another thing I'd try will be instead of breaking your program, set a breakpoint to the current position in your code+1 and enable it. That way you'll be certain the lock is not held when called – Ishay Peled Aug 24 '16 at 14:14
  • 1
    You could try something like `while $_any_caller_matches(".*malloc.*") finish end` – Mark Plotnick Aug 24 '16 at 15:28
  • @MarkPlotnick using that I get `Invalid data type for function to be called.` – PepeHands Aug 24 '16 at 15:51
  • @MarkPlotnick also my `help function` only outputs `_isvoid`, `_memeq`, `_regex`, `_streq`, `_strlen` – PepeHands Aug 24 '16 at 15:58
  • @Dima You may have an older version of gdb that doesn't have `$_any_caller_matches`. Does `show conv` show any functions that are similar to that, such as `$_caller_matches` or `$caller_matches` ? – Mark Plotnick Aug 24 '16 at 16:01
  • @MarkPlotnick no, it does not. My `gdb` version is 7.7.1. – PepeHands Aug 24 '16 at 16:03
  • Depending on the actual need, tracepoints may help you, they do not stop execution so there shouldn't be any lock issues. See here https://sourceware.org/gdb/onlinedocs/gdb/Tracepoints.html – Ishay Peled Aug 24 '16 at 16:17
  • 1
    @Dima You can get `$_any_caller_matches` from https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob_plain;f=gdb/python/lib/gdb/function/caller_is.py;hb=HEAD ; download the file and use the gdb `source` command to read it in. I've verified that, at least for your demo program, the `while $_any_caller_matches(".*malloc.*") finish end` code works; it'll match functions like `__GI___libc_malloc` and `_int_malloc`. – Mark Plotnick Aug 24 '16 at 19:21
  • @MarkPlotnick thanks! With python it works for me as well! – PepeHands Aug 24 '16 at 20:22
1

If the 'finish' solution doesn't work for you. Here is another idea.

You can check if you are in malloc when you break the program. Based on the boolean in/out you skip calling the print commands. Here is a working example.

# gdb script: pygdb-logg.gdb
# easier interface for pygdb-logg.py stuff
# from within gdb: (gdb) source -v pygdb-logg.gdb
# from cdmline: gdb -x pygdb-logg.gdb -se test.exe

# first, "include" the python file:
source -v pygdb-logg.py

# define shorthand for inMalloc():
define inMalloc
  python inMalloc()
end

The associated python file:

# gdb will 'recognize' this as python
#  upon 'source pygdb-logg.py'
# however, from gdb functions still have
#  to be called like:
#  (gdb) python print logExecCapture("bt")

import sys
import gdb
import os

def logExecCapture(instr):
  # /dev/shm - save file in RAM
  ltxname="/dev/shm/c.log"

  gdb.execute("set logging file "+ltxname) # lpfname
  gdb.execute("set logging redirect on")
  gdb.execute("set logging overwrite on")
  gdb.execute("set logging on")
  gdb.execute("bt")
  gdb.execute("set logging off")

  replyContents = open(ltxname, 'r').read() # read entire file
  return replyContents

# in malloc?
def inMalloc():
  isInMalloc = -1;
  # as long as we don't find "Breakpoint" in report:
  while isInMalloc == -1:
    REP=logExecCapture("n")
#Look for calls that have '_malloc' in them 
    isInMalloc = REP.find("_malloc")
    if(isInMalloc != -1):
#       print ("Malloc:: ", isInMalloc, "\n", REP)
       gdb.execute("set $inMalloc=1")
       return True
    else:
#       print ("No Malloc:: ", isInMalloc, "\n", REP)
       gdb.execute("set $inMalloc=0")
       return False

gdb -x pygdb-logg.gdb -se test.exe

From the command line or script,

(gdb) inMalloc
(gdb) print $inMalloc

From an actual test program:

Program received signal SIGINT, Interrupt.
0x00007ffff7a94dba in _int_malloc (av=<optimized out>, bytes=1) at malloc.c:3806
3806    malloc.c: No such file or directory.
(gdb) inMalloc
(gdb) if $inMalloc
 >print $inMalloc
 >end
$1 = 1

I believe your script can use a similar 'if' structure to do/not do printf

Most of this was knocked off from here

Community
  • 1
  • 1
Matthew Fisher
  • 2,258
  • 2
  • 14
  • 23
  • Thanks! Even though I need to do a bit more complicated things your approach gave me some useful ideas. – PepeHands Aug 25 '16 at 11:03