• Debugging with GDB. Basics of using the WinDbg debugger Debugger gdb commands

    The purpose of debugging a program is to eliminate errors in its code. To do this, you will most likely have to examine the state of the variables in lead time, as well as the execution process itself (for example, tracking conditional branches). Here the debugger is our first assistant. Of course, C has a lot of debugging options without directly stopping the program: from simple printf(3) to special network logging systems and syslog. In assembler, such methods are also applicable, but you may need to monitor the state of registers, dump images of RAM, and other things that are much more convenient to do in an interactive debugger. In general, if you write in assembly language, then you are unlikely to do without a debugger.

    You can start debugging by identifying a breakpoint if you already know approximately what part of the code you need to examine. This method is used most often: we set a breakpoint, run the program and go through its execution step by step, simultaneously observing the necessary variables and registers. You can also simply run the program under a debugger and catch the moment when it crashes due to a segmentation fault - this way you can find out which instruction is trying to access memory, take a closer look at the variable causing the error, and so on. Now you can examine this code again, go through it step by step, setting a breakpoint a little before the moment of failure.

    Let's start with something simple. Let's take the Hello world program and compile it with debugging information using the -g compiler switch:

    $ gcc -g hello.s -o hello $

    Launch gdb:

    $ gdb ./hello GNU gdb 6.4.90-debian Copyright (C) 2006 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb)

    GDB has started, loaded the program under study, displayed a prompt (gdb) and is waiting for commands. We want to go through the program "step by step" (single-step mode). To do this, you need to specify the command at which the program should stop. You can specify a subroutine - then the stop will be carried out before the execution of the instructions of this subroutine begins. You can also specify the file name and line number.

    (gdb) b main Breakpoint 1 at 0x8048324: file hello.s, line 17. (gdb)

    b is short for break. All commands in GDB can be abbreviated as long as this does not create ambiguity. We launch the program with the run command. The same command is used to restart a previously running program.

    (gdb) r Starting program: /tmp/hello Breakpoint 1, main () at hello.s:17 17 movl $4, %eax /* put system call number write = 4 Current language: auto; currently asm (gdb)

    GDB has stopped the program and is waiting for commands. You see the command in your program that will be executed next, the name of the function that is currently executing, the file name, and the line number. For step-by-step execution, we have two commands: step (abbreviated s) and next (abbreviated n). The step command executes the program by entering the bodies of subroutines. The next command steps only the instructions of the current subroutine.

    (gdb) n 20 movl $1, %ebx /* first parameter goes to register %ebx */ (gdb)

    So, the instruction on line 17 is executed, and we expect the number 4 to be in the %eax register. To print various expressions on the screen, we use the print command (abbreviated p ). Unlike assembler commands, GDB uses the $ sign instead of % when writing registers. Let's see what's in the %eax register:

    (gdb) p $eax $1 = 4 (gdb)

    Indeed 4! GDB numbers all output expressions. Now we see the first expression ($1), which is equal to 4. Now this expression can be accessed by name. You can also make simple calculations:

    (gdb) p $1 $2 = 4 (gdb) p $1 + 10 $3 = 14 (gdb) p 0x10 + 0x1f $4 = 47 (gdb)

    While we were playing with the print command, we had already forgotten which instruction was executed next. The info line command displays information about the specified line of code. Without arguments, prints information about the current line.

    (gdb) info line Line 20 of "hello.s" starts at address 0x8048329 and ends at 0x804832e . (gdb)

    The list command (abbreviated l ) displays the source code of your program. You can pass it as arguments:

    • line_number- line number in the current file;
    • file:line_number- line number in the specified file;
    • function_name- function name, if there is no ambiguity;
    • file:function_name- name of the function in the specified file;
    • *address- the address in memory where the required instruction is located.

    If you pass one argument, the list command will print 10 lines of source code around that location. By passing two arguments, you specify the start line and the end line of the listing.

    (gdb) l main 12 outside of this file */ 13 .type main, @function /* main - function (not data) */ 14 15 16 main: 17 movl $4, %eax /* put system call number 18 write = 4 in register %eax */ 19 20 movl $1, %ebx /* place the first parameter in register 21 %ebx; file descriptor number 22 stdout = 1 */ (gdb) l *$eip 0x8048329 is at hello.s:20. 15 16 main: 17 movl $4, %eax /* place the system call number 18 write = 4 in the register %eax */ 19 20 movl $1, %ebx /* place the first parameter in register 21 %ebx; file descriptor number 22 stdout = 1 */ 23 movl $hello_str, %ecx /* place the second parameter in register 24 %ecx; pointer to a string */ (gdb) l 20, 25 20 movl $1, %ebx /* place the first parameter in register 21 %ebx; file descriptor number 22 stdout = 1 */ 23 movl $hello_str, %ecx /* place the second parameter in register 24 %ecx; pointer to string */ 25 (gdb)

    Remember this command: list *$eip . With its help, you can always view the source code around the instruction currently being executed. Let's run our program further:

    (gdb) n 23 movl $hello_str, %ecx /* place the second parameter in the %ecx register (gdb) n 26 movl $hello_str_length, %edx /* place the third parameter in the %edx register (gdb)

    Isn't it tedious to press n every time? If you just press Enter, GDB will repeat the last command:

    (gdb) 29 int $0x80 /* call interrupt 0x80 */ (gdb) Hello, world! 31 movl $1, %eax /* system call number exit = 1 */ (gdb)

    Another handy command worth knowing about is info registers. Of course, it can be shortened to i r . You can pass it a parameter - a list of registers that need to be printed. For example, when execution occurs in protected mode, we are unlikely to be interested in the values ​​of segment registers.

    (gdb) info registers eax 0xe 14 ecx 0x804955c 134518108 edx 0xe 14 ebx 0x1 1 esp 0xbfabb55c 0xbfabb55c ebp 0xbfabb5a8 0xbfabb5a8 esi 0x0 0 edi 0xb7f6bcc0 - 1208566592 eip 0x804833a 0x804833a eflags 0x246 [ PF ZF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) info registers eax ecx edx ebx esp ebp esi edi eip eflags eax 0xe 14 ecx 0x804955c 134518108 edx 0xe 14 ebx 0x1 1 esp 0xbfabb55c 0xbfabb55c ebp 0xbfabb5a8 0xbfabb5a8 esi 0x0 0 edi 0xb7f6bcc0 -1208566592 eip 0x804833a 0x804833a eflags 0x246 [PF ZF IF] (gdb)

    So, in addition to registers, we also have

    G.D.B. refers to “smart” debugger programs, that is, those that “understand” the code and are able to execute it line by line, change the values ​​of variables, set breakpoints and stopping conditions... In a word, do everything so that the developer can check the correct operation of his program .

    G.D.B. built into many UNIX-like systems and can debug several programming languages. Xi is among them.

    To call G.D.B. enter the command in the terminal

    Gdb [name of the program you want to debug]

    To get out G.D.B.: enter the command

    Quit or C–d

    Other important GDB commands

    run [program command line arguments] Run the program for execution. break [line number/function name] Set a program breakpoint at a specific line or function. next Go to the next line without going inside the functions. step Go to the next line. If there is a function call on the line, go inside it. list Print a fragment of the program code (several lines around the place where the point is currently set) print [ variable] Print the value of the variable on the screen. info locals Print the current values ​​of all local variables inside a loop, function, etc. display [variable] Display the value of a variable at each debugging step. help Show a list of all GDB commands.

    Let's look at how to work with GDB using the caesar.c program, which you most likely wrote last week. We will test it on our own version, so your results may differ slightly depending on the implementation.

    So, go to the pset2 folder (we think you already remember how to do this) in the “cs50 Virtual Laboratory” or CS50 IDE. Enter the command:

    Gdb. /caesar

    The caesar program has one function, main. Let's set the program's breakpoint at the main function:

    break main

    Let's run the caesar program with argument "3":

    Run 13

    Let's say we need to check the value of argc:

    Print argc

    This is what it should look like in a terminal window:

    Now we execute the program step by step using the next command. Let's do it several times.

    Here the key variable is assigned a value. Let's check what meaning it has to this line:

    The first time next is called, the key variable is set to "0". Why is this so if we entered the number 3? The point is that the command has not yet been executed. When you enter next a few more times, the program prompts you to enter text.

    By executing the next command again, we will go inside the loop with a condition.

    Let's talk about debuggers for Microsoft Windows. There are quite a lot of them, just remember everyone’s favorite OllyDbg, the once popular but now practically dead SoftIce, as well as Sycer, Immunity Debugger, x64dbg and countless number of debuggers built into the IDE. According to my observations, not everyone likes WinDbg. I think this is mainly due to the debugger command interface. Fans of Linux and FreeBSD would undoubtedly like it. But hardcore Windows users find it strange and inconvenient. Meanwhile, in terms of functionality, WinDbg is in no way inferior to other debuggers. At a minimum, it is definitely no worse than classic GDB or LLDB. This is what we will see today.

    In the world of Windows, everything, as usual, is a bit of a mess. The official WinDbg installer can be downloaded from the MS website. In addition to WinDbg, this installer will also install the latest version of the .NET Framework and reboot the system without asking. After installation, it is not a fact that the debugger will work at all, especially under older versions of Windows. Therefore, it is better to download an unofficial build of WinDbg or . Urgently I advise you to use one of these versions - this is the easiest and fastest way to install WinDbg.

    There are two versions of WinDbg, x86 and x64. To avoid any problems, debug x86 applications using the x86 debugger, and x64 applications using the x64 debugger. After the first launch, WinDbg will look pretty poor. But don't worry about it. After working with WinDbg for just a few minutes, you will customize it for yourself and it will look quite nice. For example, something like this (clickable, 51 KB, 1156 x 785):

    This screenshot shows debugging the program from the note Getting a list of running processes on the Windows API. As you can see, WinDbg picked up the source code of the program. The values ​​of local variables are displayed on the right. At the bottom there is a window for entering commands, where the call stack was displayed using the kn command. At the top of the debugger are buttons that allow you to perform simple actions like “step forward” and also open additional windows. Using these windows you can view the contents of RAM, register values, disassembler listing of the program and many other interesting things.

    In general, a lot of things in WinDbg can be done through the GUI. For example, in the source code window, you can place the cursor at the desired location and click on the palm icon to create a breakpoint there. Or execute run to cursor. You can also stop the execution of the command at any time using the Break button in the panel at the top. We will not dwell on all this in detail. But keep in mind that such opportunities exist and are worth exploring!

    Before you start debugging using WinDbg, you need to do a few simple steps. Open File → Symbol File Path and enter:

    SRV*C:\symbols*http://msdl.microsoft.com/download/symbols

    Then click Browse and specify the path to the debug information files (.pdb) of your project. Similarly, in File → Source File Path, specify the path to the source directory. If in doubt, specify the path where the Visual Studio project file is located, you can't go wrong. Then say File → Save Workspace so that all these paths do not have to be specified again every time you start WinDbg.

    Now that everything is set up, there are several ways to start debugging. You can launch a new process under the debugger, you can connect to an existing one, you can open a crash dump. All this is done through the File menu. The possibility of remote debugging deserves special attention. For example, if you debug drivers using WinDbg, then you don’t have much choice but to use remote debugging. Which is not surprising, since the slightest error in the driver code can lead to a BSOD.

    If you are already debugging the process, but would like to start doing it remotely, we say:

    Server tcp:port=3003

    You need to check that the port is open, which is especially important on Windows Server. On the client, do File → Connect to a Remote Session, enter:

    tcp:Port=3003,Server=10.110.0.10

    In addition, you can start a server that allows you to debug any process in the system:

    dbgsrv.exe -t tcp:port=3003

    On the client we connect via File → Connect to Remote Stub. After this, through the interface you can select a process from the list as usual or start a new one. Only the processes will run on the remote machine.

    Now, finally, let's look at the basic WinDbg commands.

    Important! Sometimes commands can take a very long time to complete, for example if you decide to load all the debugging symbols at once. If you get tired of waiting, just press Ctr+C in the command input field, and WinDbg will immediately stop doing what it is doing now.

    Help
    .hh command

    Clear output in the Command window:

    Add the path where WinDbg will search for debugging symbols (the same command without the plus sign will overwrite all previously entered paths):

    Sympath+ c:\pdbs

    Reload symbols:

    Show module list:

    We are looking for symbols:

    x *!Ololo::My::Namespace::*

    Load symbols for the module:

    ld module_name

    If WinDbg for some reason does not find .pdb files and in the stacktraces instead of the names of .c/.cpp files with line numbers you see something like module+0x19bc, try running the following sequence of commands - this will give you more information about the possible causes of the problem:

    Sym noisy
    .reload MyModule.dll

    Specify the path to the source directory:

    Set a breakpoint:

    bp kernel32!CreateProcessA
    bp `mysorucefile.cpp:123`
    bp `MyModule!mysorucefile.cpp:123`
    bp @@(Full::Class:Name::method)

    Set a breakpoint that will only work once:

    Set a breakpoint that will work the 5th time, after 4 passes:

    You can automatically execute commands every time a breakpoint is hit:

    bp kernel32!LoadLibraryA ".echo \"Variables:\n\"; dv"

    U hardware breakpoint syntax is:

    ba

    Where mode is e, r or w - execution, read, write. When mode = e the size parameter can only be 1. For example:

    ba e 1 kernel32!LoadLibraryA

    List of breakpoints:

    Disable breakpoint:

    Activate a breakpoint:

    Completely removing breakpoints:

    bc number
    bc*

    Show the commands you need to enter to restore current breakpoints:

    Write log to file:

    Logopen c:\1.txt

    Stop writing log to file:

    Execute commands from file:

    To save breakpoints to a file, you can run (necessarily in one line!) the following commands:

    Logopen c:\1.txt; .bpcmds; .logclose

    Show disassembler listing:

    Show register values:

    Show call stack:

    Same thing with frame numbers:

    Move to frame:

    Frame number

    Show local variables:

    Show structure:

    dt variable_name

    Show structure recursively:

    dt -r variable_name

    Memory dump at:

    Memory dump in the form word/dword/qword:

    dw address
    dd address
    dq address

    Dump in the form of bits:

    Dump ascii string:

    Unicode string dump:

    Memory editing.

    Today you will take another step in business
    studying Linux systems. I'll tell you about the main ones
    techniques for working with gdb. Having mastered them, you will be able to understand how any program works and write your own exploits.

    You've probably all heard about such a thing as a debugger; gdb is a debugger. GDB-GNU
    Debugger. This is a kind of SoftICE for Windows (for those who don’t know, it’s the most popular and, in my opinion, generally the best debugger), only under
    Linux systems. The fact is that there are not many documents on the network that demonstrate the operation of this thing, and at one time I mastered it myself. So,
    The document will cover the basic gdb commands. All this will be shown with an example. And as an example, I decided to take the unnecessary program yes. For those who don’t know, this program simply prints the ‘y’ character ad infinitum. To begin with, I decided to teach it to print not this character, but the string ‘XAKEP’, at least it will be more fun.

    Well, now everything is in order. The debugger itself starts like this:

    But you can enter various parameters, for us this will be the path to the program under study:

    # gdb /usr/bin/yes

    You can examine core files, to do this you need to enter the following:

    # gdb /usr/bin/yes core

    You may also need a command to view the contents of the registers:

    (gdb) info registers

    or like this (short version)

    Now let's look at how to make interceptions. There are
    breakpoints, interception points and watchpoints. More specifically, I'd like to talk about breakpoints. They can be installed on:

    (gdb) break function - Stop before entering a function
    (gdb) break *adress - Stop before executing the address instruction.

    After the settings, you can view all the points, use the command:

    (gdb) info break

    And then you can delete these points:

    (gdb) clear breakpoint - where break is the name of the breakpoint
    (e.g. function or address)

    A very necessary thing is the ability to automatically display different values ​​while executing a program. There is a display command for this:

    (gdb) display/format value , where format is the display format, and value is the expression itself that needs to be displayed.

    The following commands are provided to work with the display:

    (gdb) info display - displays information about displays
    (gdb) delete num - where num – delete elements with index
    num

    This was a short reference to the commands to get the basic idea.
    Next, I would like to demonstrate this and a little more with an example. And remember - here I have given only a very small part of all the capabilities of gdb, in fact it has hundreds of times more, so read and learn.
    As I promised, we take the unnecessary yes program. The path on your machine may not coincide with mine, it all depends on the operating system you are using, if anything, use the search (command
    find).

    # gdb /usr/bin/yes

    Once launched it says a welcome message.

    GNU gdb 19991004




    There is absolutely no warranty for GDB. Type "show warranty" for details.
    This GDB was configured as "i386-redhat-linux"...
    (no debugging symbols found)...

    Since yes outputs an infinite number of symbols, it would be better for us not to see them in the debugger, but the output
    programs can be directed to another console. Open a new terminal, type who is i and you will get the console name. Should come out
    something like this:

    Now we just tie it to it.

    (gdb) tty /dev/pts/1

    Now we set a breakpoint on the puts() function, and to make it clearer, here is a man help about the function (man command
    puts)

    #include
    int puts(const char *s);
    puts() writes the string s and a trailing newline to std
    out.

    As you can see, the function sends the string s to the output stream. That's what we need. We'll stop there for now.

    (gdb) break puts
    Breakpoint 1 at 0x8048698

    And we launch the program itself to wait until gdb stops its execution at the function call.

    (gdb)r
    Starting program: /usr/bin/yes
    Breakpoint 1 at 0x4006d585: file ioputs.c, line 32.

    Breakpoint 1, 0x4006d585 in _IO_puts (str=0x8048e59 "y") at ioputs.c:32
    32 ioputs.c: No such file or directory.
    1: x/i $eip 0x4006d585<_IO_puts+21>: mov 0x8(%ebp),%esi

    Oh, a miracle happened, the breakpoint worked. What we see - and we see nothing more than a function parameter, or rather the address at which it lies. What do you need now?
    do? That's right, correct the data at this address. At the same time, we will overwrite a couple more symbols with our own.

    (gdb) set (char)0x8048e59="X"
    (gdb) set (char)0x8048e5a="A"
    (gdb) set (char)0x8048e5b="K"
    (gdb) set (char)0x8048e5c="E"
    (gdb) set (char)0x8048e5d="P"

    Well, now let's look at our creation. What lies in memory:

    (gdb) x/3sw 0x8048e59
    0x8048e59<_IO_stdin_used+437>: "XAKEP\004\b"
    0x8048e61<_IO_stdin_used+445>: ""
    0x8048e62<_IO_stdin_used+446>: ""

    Now let's remove our breakpoint:

    (gdb) info break
    Num Type Disp Enb Address What
    1 breakpoint keep y 0x4006d585 in _IO_puts at ioputs.c:32
    breakpoint already hit 1 time
    (gdb) clear puts
    Deleted breakpoint 1

    And let’s continue execution to enjoy the result:

    That's it. Let's go out.

    (gdb)q
    The program is running. Exit anyway? (y or n)y

    This is where the practice ends, study the rest yourself and remember that the main thing in this life is LEARNING.
    Here are some more examples of work:

    Attaching to a running process:

    //launch gdb
    hack@exploit:~ > gdb
    GNU gdb 4.18
    Copyright 1998 Free Software Foundation, Inc.
    GDB is free software, covered by the GNU General Public License, and you are
    welcome to change it and/or distribute copies of it under certain conditions.
    Type "show copying" to see the conditions.
    There is absolutely no warranty for GDB. Type "show warranty" for
    details.
    This GDB was configured as "i386-suse-linux".
    (gdb) attach "pid"
    (gdb) 1127 // attach example

    Search in memory:

    (gdb) x/d or x "address" show decimal
    (gdb) x/100s "address" show next 100 decimals
    (gdb) x 0x0804846c show decimal at 0x0804846c
    (gdb) x/s "address" show strings at address
    (gdb) x/105 0x0804846c show 105 strings at 0x0804846c
    (gdb) x/x "address" show hexadecimal address
    (gdb) x/10x 0x0804846c show 10 addresses at 0x0804846c
    (gdb) x/b 0x0804846c show byte at 0x0804846c
    (gdb) x/10b 0x0804846c-10 show byte at 0x0804846c-10
    (gdb) x/10b 0x0804846c+20 show byte at 0x0804846c+20
    (gdb) x/20i 0x0804846c show 20 assembler instructions at address

    List of all sections in the executable file:

    (gdb) maintenance info sections // or
    (gdb) mai i s

    Executable file:
    `/home/hack/homepage/challenge/buf/basic", file type
    elf32-i386.
    0x080480f4->0x08048107 at 0x000000f4: .interp ALLOC

    0x08048108->0x08048128 at 0x00000108: .note.ABI-tag
    ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x08048128->0x08048158 at 0x00000128: .hash ALLOC
    LOAD READONLY DATA HAS_CONTENTS
    0x08048158->0x080481c8 at 0x00000158: .dynsym ALLOC
    LOAD READONLY DATA HAS_CONTENTS
    0x080481c8->0x08048242 at 0x000001c8: .dynstr ALLOC
    LOAD READONLY DATA HAS_CONTENTS
    0x08048242->0x08048250 at 0x00000242: .gnu.version
    ALLOC LOAD READONLY DATA
    HAS_CONTENTS

    Breaking point to the address:

    (gdb) disassemble main
    Dump of assembler code for function main:
    0x8048400

    : push %ebp
    0x8048401 : mov %esp,%ebp
    0x8048403 : sub $0x408,%esp
    0x8048409 : add $0xfffffff8,%esp
    0x804840c : mov 0xc(%ebp),%eax
    0x804840f : add $0x4,%eax
    0x8048412 : mov (%eax),%edx
    0x8048414 : push %edx
    0x8048415 : lea 0xfffffc00(%ebp),%eax
    ...

    (gdb) break *0x8048414 // example
    Breakpoint 1 at 0x8048414
    (gdb) break main // example
    Breakpoint 2 at 0x8048409
    (gdb)

    Translation of Allan O'Donnell's article Learning C with GDB.

    Given the nature of high-level languages ​​like Ruby, Scheme, or Haskell, learning C can be challenging. In addition to overcoming low-level C features such as manual memory management and pointers, you also have to get by without a REPL. Once you get used to exploratory programming in a REPL, dealing with the write-compile-run cycle can be a little frustrating.

    It recently occurred to me that I could use GDB as a pseudo-REPL for C. I experimented with using GDB as a tool for learning the language rather than just debugging, and it turned out to be a lot of fun.

    The purpose of this post is to show you that GDB is a great tool for learning C. I'll introduce you to a few of my favorite commands from GDB, and demonstrate how you can use GDB to understand one of the tricky parts of the C language: the difference between arrays and pointers.

    Introduction to GDB

    Let's start by creating the following small C program - minimal.c:

    Int main() ( int i = 1337; return 0; )
    Please note that the program does absolutely nothing, and does not even have a single command printf. Now let's plunge into the new world of learning C using GBD.

    Let's compile this program with the flag -g to generate debugging information that GDB will work with, and give it this very information:

    $ gcc -g minimal.c -o minimal $ gdb minimal
    You should now be at the GDB command prompt in a flash. I promised you a REPL, so here's what you get:

    (gdb) print 1 + 2 $1 = 3
    Marvelous! print is a built-in GDB command that evaluates the result of a C expression. If you don't know what exactly a GDB command does, just get help - type help name-of-the-command on the GDB command line.

    Here's a more interesting example:

    (gbd) print (int) 2147483648 $2 = -2147483648
    I'll miss explaining why 2147483648 == -2147483648 . The main point here is that even arithmetic can be tricky in C, and GDB understands C arithmetic perfectly.

    Now let's put a breakpoint in the function main and run the program:

    (gdb) break main (gdb) run
    The program stopped on the third line, right where the variable is initialized i. The interesting thing is that although the variable has not yet been initialized, we can already see its value using the command print:

    (gdb) print i $3 = 32767
    In C, the value of a local uninitialized variable is undefined, so the result you get may vary.

    We can execute the current line of code using the command next:

    (gdb) next (gdb) print i $4 = 1337

    We explore memory using the command X

    Variables in C are contiguous blocks of memory. In this case, the block of each variable is characterized by two numbers:

    1. Numeric address of the first byte in the block.
    2. Block size in bytes. This size is determined by the type of the variable.

    One of the distinctive features of the C language is that you have direct access to a variable's memory block. Operator & gives us the address of the variable in memory, and sizeof calculates the size occupied by a memory variable.

    You can play with both possibilities in GDB:

    (gdb) print &i $5 = (int *) 0x7fff5fbff584 (gdb) print sizeof(i) $6 = 4
    In normal language, this means that the variable i located at 0x7fff5fbff5b4 and takes up 4 bytes in memory.

    I already mentioned above that the size of a variable in memory depends on its type, and generally speaking, the operator sizeof can operate with the data types themselves:

    (gdb) print sizeof(int) $7 = 4 (gdb) print sizeof(double) $8 = 8
    This means that, at least on my machine, variables like int occupy four bytes, and type double– eight bytes.

    GDB has a powerful tool for directly examining memory - the command x. This command tests memory starting from a specific address. It also has a number of formatting commands that give you precise control over how many bytes you want to check and how you want to display them. In case of any difficulties, dial help x on the GDB command line.

    As you already know, operator & calculates the address of a variable, which means that it can be passed to the command x meaning &i and thereby get the opportunity to look at the individual bytes hiding behind the variable i:

    (gdb) x/4xb &i 0x7fff5fbff584: 0x39 0x05 0x00 0x00
    The formatting flags indicate that I want four ( 4 ) values ​​output in hexadecimal (he x) in the form of one byte ( b yte). I specified checking only four bytes, because that is how much the variable takes up in memory i. The output shows the byte-by-byte representation of the variable in memory.

    But there is one subtlety associated with byte-byte output that needs to be constantly kept in mind - on Intel machines, bytes are stored in order “from least significant to highest” (from right to left), in contrast to the more familiar recording for humans, where the lowest byte would have to be in end (from left to right).

    One way to clarify this issue is to assign a variable i more interesting value and check this memory area again:

    (gdb) set var i = 0x12345678 (gdb) x/4xb &i 0x7fff5fbff584: 0x78 0x56 0x34 0x12

    Exploring memory with the team ptype

    Team ptype perhaps one of my favorites. It shows the type of C expression:

    (gdb) ptype i type = int (gdb) ptype &i type = int * (gdb) ptype main type = int (void)
    Types in C can get complex, but ptype allows you to explore them interactively.

    Pointers and Arrays

    Arrays are a surprisingly subtle concept in C. The point of this point is to write a simple program and then run it through GDB until arrays make some sense.

    So we need program code with an array array.c:

    Int main() ( int a = (1, 2, 3); return 0; )
    Compile it with the flag -g, run in GDB, and with help next go to the initialization line:

    $ gcc -g arrays.c -o arrays $ gdb arrays (gdb) break main (gdb) run (gdb) next
    At this point you can display the contents of the variable and find out its type:

    (gdb) print a $1 = (1, 2, 3) (gdb) ptype a type = int
    Now that our program is properly configured in GDB, the first thing to do is use the command x to see what the variable looks like a“under the hood”:

    (gdb) x/12xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x7fff5fbff574: 0x03 0x00 0x00 0x00
    This means that the memory space for the array a starts at address 0x7fff5fbff56c. The first four bytes contain a, the next four are a, and the last four store a. Indeed, you can check and make sure that sizeof knows that a takes up exactly twelve bytes in memory:

    (gdb) print sizeof(a) $2 = 12
    Up to this point, the arrays look as they should. They have corresponding array types and store all values ​​in contiguous memory locations. However, in certain situations, arrays behave very similarly to pointers! For example, we can apply arithmetic operations to a:

    (gdb) print a + 1 $3 = (int *) 0x7fff5fbff570
    In normal words, this means that a+1 is a pointer to int, which has the address 0x7fff5fbff570. By this point you should be reflexively passing pointers to the command x, so let's see what happened:

    (gdb) x/4xb a + 1 0x7fff5fbff570: 0x02 0x00 0x00 0x00

    Please note that the address 0x7fff5fbff570 exactly four units more than 0x7fff5fbff56c, that is, the address of the first byte of the array a. Considering that the type int takes up four bytes in memory, we can conclude that a+1 points to a.

    In fact, array indexing in C is syntactic sugar for pointer arithmetic: a[i] equivalent *(a + i). You can check this in GDB:

    (gdb) print a $4 = 1 (gdb) print *(a + 0) $5 = 1 (gdb) print a $6 = 2 (gdb) print *(a + 1) $7 = 2 (gdb) print a $8 = 3 (gdb) print *(a + 2) $9 = 3
    So, we have seen that in some situations a behaves like an array, and in some cases behaves like a pointer to its first element. What's going on?

    The answer is that when an array name is used in an expression in C, it “decays” to a pointer to the first element. There are only two exceptions to this rule: when the array name is passed to sizeof and when the array name is used with the address take operator & .

    The fact that the name a does not decay to a pointer to the first element when using the operator & , raises an interesting question: what is the difference between the pointer into which the a And &a?

    Numerically, they both represent the same address:

    (gdb) x/4xb a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 (gdb) x/4xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00
    However, their types are different. As we have already seen, the name of an array resolves to a pointer to its first element and therefore must be of type int *. As for the type &a, then we can ask GDB about it:

    (gdb) ptype &a type = int (*)
    To put it simply, &a is a pointer to an array of three integers. This makes sense: a does not disintegrate when transferred to the operator & and a has type int.

    You can trace the difference between the pointer that splits into a and surgery &a Here's an example of how they behave with respect to pointer arithmetic:

    (gdb) print a + 1 $10 = (int *) 0x7fff5fbff570 (gdb) print &a + 1 $11 = (int (*)) 0x7fff5fbff578
    Note that adding 1 to a increases the address by four units, while adding 1 to &a adds twelve to the address.

    The pointer that actually decays to a looks like &a:

    (gdb) print &a $11 = (int *) 0x7fff5fbff56c

    Conclusion

    I hope I've convinced you that GDB is an elegant exploration environment for learning C. It allows you to print the meaning of expressions using the command print, explore memory byte-by-byte with the command x and work with types using the command ptype.

    1. Use GDB to work on The Ksplice Pointer Challenge.
    2. Understand how structures are stored in memory. How do they relate to arrays?
    3. Use GDB disassembler commands to gain a better understanding of assembly language programming. It's especially fun to explore how the function call stack works.
    4. Check out the “TUI” mode of GDB, which provides a graphical ncurses add-on over the usual GDB. On OS X, you will probably have to build GDB from source.

    From the translator: Traditionally, use LAN to indicate errors. I will be glad to receive constructive criticism.