From 10efec6176c0e7c580def9fc486133546265703d Mon Sep 17 00:00:00 2001 From: Kevin Jerebica Date: Sat, 28 Sep 2024 19:48:34 +0200 Subject: [PATCH] post: add gdb multiprocessing --- config.toml | 3 + content/posts/gdb-multiprocessing.md | 171 +++++++++++++++++++++++++++ themes/minth/assets/css/main.css | 3 +- themes/minth/static/css/global.css | 11 ++ 4 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 content/posts/gdb-multiprocessing.md diff --git a/config.toml b/config.toml index a4b1250..e6fba20 100644 --- a/config.toml +++ b/config.toml @@ -6,3 +6,6 @@ summaryLength = 20 [markup.goldmark.renderer] unsafe=true + +[markup.highlight] + style="vim" diff --git a/content/posts/gdb-multiprocessing.md b/content/posts/gdb-multiprocessing.md new file mode 100644 index 0000000..33b6505 --- /dev/null +++ b/content/posts/gdb-multiprocessing.md @@ -0,0 +1,171 @@ +--- +title: "Debugging multiprocessing software with GDB" +date: 2024-09-28T14:33:17+02:00 +--- + +## backstory + +Since ever I started the development of my high school finals project - [a proxy library](http://git.0xdeadbeer.xyz/0xdeadbeer/proxlib) - +I have had the desire to master the art of debugging multiprocessing software. +Up until now I had no idea it was even possible due to the scarse general +information available about it. + +`set follow-fork-mode [mode]` seemed promising and quite honestly, worked as expected. +Except, I realized that to debug my proxy, I was in need of a more dynamic approach such as: + - being asked whether to follow the child/parent as I catch multiple forks + - switching between the parent or multiple children instantly + - releasing the parent while I only debug the child or vice versa + +I read more about it online and it seemed as if the first of my requirements was an already supported feature +of GDB - (`set follow-fork-mode ask`) - which would ask the user at runtime whether they want to follow +the child or the parent the second they hit a fork/vfork syscall. Sadly, newer versions of GDB have had it cut off +and deprecated for whatever reason. + +The frustration got me really close to taking initiative and writing myself a little patch for the modern versions of GDB +so they would also have this useful feature. But something told me there had to be a better way. And oh boy, better way there was. + +## start + +{{< highlight c "linenos=inline" >}} +/* main.c */ + +#include +#include + +int main(void) { + fprintf(stdout, "--multiprocessing example--\n"); + for (int i = 0; i < 5; i++) { + pid_t pid = fork(); + if (pid == 0) { + fprintf(stdout, ">>hello from child: %d\n", i); + return 0; + } + if (pid < 0) { + fprintf(stderr, ">>failed forking\n"); + return -1; + } + } + + fprintf(stdout, ">>hello from parent\n"); + + return 0; +} +{{< / highlight >}} + +We are going to step through this program - getting into the nitty griddy of debugging and seeing +what options we have depending on our demands. First of all, use your compiler of choice. I am going to use +GCC and turn on debugging info. + + gcc -g3 main.c -o main + +Before we continue, make sure you at least have the following setting enabled in .gdbinit: + + set detach-on-fork off + +A very convenient option that will block execution of both the parent and the new child - not +letting one of them run unless you manually call continue. + +## following the parent + +This scenario is unsurprisingly simple. GDB does this automatically, therefore, I will not cover it. + +{{}} +[hemisquare@detached-hemi tmp]$ gdb main +(gdb) break main +Breakpoint 1 at 0x1161: file main.c, line 7. +(gdb) run +Starting program: /home/hemisquare/tmp/main +[Thread debugging using libthread_db enabled] +Using host libthread_db library "/usr/lib/libthread_db.so.1". + +Breakpoint 1, main () at main.c:7 +7 fprintf(stdout, "--multiprocessing example--\n"); +(gdb) s +--multiprocessing example-- +8 for (int i = 0; i < 5; i++) { +(gdb) +9 pid_t pid = fork(); +(gdb) +[New inferior 2 (process 51915)] +[Thread debugging using libthread_db enabled] +Using host libthread_db library "/usr/lib/libthread_db.so.1". +10 if (pid == 0) { +(gdb) p pid +$1 = 51915 +(gdb) # we are the parent +{{}} + +## following the child + +Similar to the parent example, I will step until we hit the first fork syscall. + +{{}} +[hemisquare@detached-hemi tmp]$ gdb main +(gdb) break main +Breakpoint 1 at 0x1161: file main.c, line 7. +(gdb) run +Starting program: /home/hemisquare/tmp/main +[Thread debugging using libthread_db enabled] +Using host libthread_db library "/usr/lib/libthread_db.so.1". + +Breakpoint 1, main () at main.c:7 +7 fprintf(stdout, "--multiprocessing example--\n"); +(gdb) s +--multiprocessing example-- +8 for (int i = 0; i < 5; i++) { +(gdb) +9 pid_t pid = fork(); +(gdb) +[New inferior 2 (process 52175)] +[Thread debugging using libthread_db enabled] +Using host libthread_db library "/usr/lib/libthread_db.so.1". +10 if (pid == 0) { +(gdb) +{{}} + +So, currently we are inside the parent "inferior". And inferior is just a GDB term for processes, threads, or whatever it is that you are debugging. +Through forks, we create another inferior which we can switch to. As you can see, we now have two inferiors: + +{{}} +(gdb) info inferiors + Num Description Connection Executable +* 1 process 52172 1 (native) /home/hemisquare/tmp/main + 2 process 52175 1 (native) /home/hemisquare/tmp/main +(gdb) +{{}} + +The '*' indicates the inferior we are currently residing in. Inferior 2 is the newborn child we just forked. +Let's switch into the child! + +{{}} +(gdb) inferior 2 +[Switching to inferior 2 [process 52175] (/home/hemisquare/tmp/main)] +[Switching to thread 2.1 (Thread 0x7ffff7dab740 (LWP 52175))] +#0 0x00007ffff7e90b57 in _Fork () from /usr/lib/libc.so.6 +(gdb) finish +Run till exit from #0 0x00007ffff7e90b57 in _Fork () from /usr/lib/libc.so.6 +0x00007ffff7e966e2 in fork () from /usr/lib/libc.so.6 +(gdb) finish +Run till exit from #0 0x00007ffff7e966e2 in fork () from /usr/lib/libc.so.6 +0x0000555555555192 in main () at main.c:9 +9 pid_t pid = fork(); +(gdb) nexti +10 if (pid == 0) { +(gdb) p pid +$1 = 0 +(gdb) s +11 fprintf(stdout, ">>hello from child: %d\n", i); +(gdb) +>>hello from child: 0 +12 return 0; +(gdb) +{{}} + +As you can see, here, we were able to step through the code of the child. Notice that the parent is still handing where we +left it at. It is totally okay to now switch back into the parent inferior and continue the execution from there. + +## conclusion + +Knowing this is essential for debugging my proxy library. I am thankful I now know GDB just a little better and was +hopefully able to provide you with a useful learning resource. I might extend this article in case I master more +interesting multiprocessing or multithreading techniques. diff --git a/themes/minth/assets/css/main.css b/themes/minth/assets/css/main.css index 9bd4766..ac7ef63 100644 --- a/themes/minth/assets/css/main.css +++ b/themes/minth/assets/css/main.css @@ -11,10 +11,9 @@ body { /*background: linear-gradient(0deg, rgba(0,78,255,1) 0%, rgba(0,0,0,1) 100%); */ background-image: url('../hacker-manifesto-bg.jpg'); - background-repeat: repeat;*/ + background-repeat: repeat; /*background-size: contain; background-attachment: fixed;*/ - margin: 0; } @media only screen and (min-width: 800px) { diff --git a/themes/minth/static/css/global.css b/themes/minth/static/css/global.css index 529f407..ebf8d48 100644 --- a/themes/minth/static/css/global.css +++ b/themes/minth/static/css/global.css @@ -11,6 +11,11 @@ color: white; } +pre code { + display: block; + background: black; +} + .post-title { font-size: 200px; } @@ -86,3 +91,9 @@ code { img { width: 100%; } + +.code { + background: #ffffff !important; + color: black !important; +} +