6.5. Examining Data, Memory, and Registers

6.5. Examining Data, Memory, and Registers

At this point, we’ve covered all of the ways to get a process (or a memory image of a process) into GDB. In this section, we discuss how to examine data, memory, and registers. This section introduces important GDB commands that work regardless of whether the process is debugging a live process or whether you’re performing post-mortem with a core file. When there is a difference, there will be a note to indicate it.

在這一點上, 我們已經介紹了所有的方法來獲取一個進程 (或一個進程的內存映像) 到 GDB。在本節中, 我們將討論如何檢查數據、內存和寄存器。本節介紹了重要的 GDB 命令, 不管該進程是調試實時進程還是使用核心文件。當有差異時, 將會有一個註釋來表示它。

6.5.1. Memory Map

Strictly speaking, viewing the memory map for a process is not part of GDB but is still very important to understand, which is why it is covered briefly here in the context of GDB and in more detail in Chapter 3, “The /proc Filesystem.” There must be a live process for there to be a corresponding “maps” file under /proc. You cannot get a memory map using /proc for a process image that is loaded in GDB as a core file.

嚴格地說, 查看進程的內存映射不是 GDB 的一部分, 但仍然是非常重要的, 這就是爲什麼它在 GDB 的上下文中簡要介紹, 以及在3章 "/proc文件系統" 中更詳細地介紹。一個實時的進程, 在/proc有一個對應的 "maps" 文件。使用/proc 內存映射, 您無法獲得可以在GDB 中加載的獲得core文件。

The memory map is the list of memory segments (aka regions) that a process has in its address space. There is a memory segment for every type of memory that a process is using, including the process heap, the process stack, memory that stores the contents of an executable, memory for the shared libraries, and so on. Memory segments also have different attributes such as read, write, and execute. These attributes will depend on the purpose of the memory segment. Shared libraries, for example, will have a large read/execute segment that cannot be written to. This is for the machine code (the actual code that gets run) in the shared library.

內存映射是進程在其地址空間中具有的內存段 (又稱區域) 的列表。對於進程所使用的每種類型的內存, 都有一個內存段, 包括進程堆、進程棧、存儲可執行文件內容的內存、共享庫的內存等。內存段還具有不同的屬性, 如讀取、寫入和執行。這些屬性將取決於內存段的用途。例如, 共享庫將具有一個無法寫入的巨大的讀取/執行段。這是用於共享庫中的機器代碼 (實際運行的代碼)。

The memory map is important for a few reasons: 內存映射很重要, 原因有以下幾個:

  • It tells you which shared libraries are loaded and at which addresses.
  • It tells you where each memory segment is. Memory accessed outside of the valid memory segments will cause a segmentation violation.
  • The memory map will tell you a bit more about an address. If the address is in the memory map for a shared library, it is probably a global variable or function in that shared library.
  • You can tell if the heap or stack collided with another memory segment (for example, there is no space between the heap or stack and the next segment).

The best way to look at the memory map for a live process in GDB is to use a shell escape, making use of GDB’s info program and shell commands (the latter is to make direct calls to programs outside of GDB):

在 gdb 中查看實時進程的內存映射的最好方法是使用 shell 轉義, 利用 gdb 的信息程序和 shell 命令 (後者直接調用 gdb 外部的程序):

Code View: Scroll / Show All

(gdb) info program

    Using the running image of attached process 11702.

Program stopped at 0x4019fd01.

It stopped with signal SIGSTOP, Stopped (signal).

(gdb) shell cat /proc/11702/maps

08048000-08049000  r-xp 00000000 08:13 3647282  /home/wilding/src/Linuxbook/hang2

08049000-0804a000  rw-p 00000000 08:13 3647282  /home/wilding/src/Linuxbook/hang2

40000000-40012000 r-xp 00000000 08:13 1144740  /lib/ld-2.2.5.so

40012000-40013000 rw-p 00011000 08:13 1144740  /lib/ld-2.2.5.so

40013000-40014000 rw-p 00000000 00:00 0

40014000-400ad000 r-xp 00000000 08:13 1847971  /usr/lib/libstdc++.so.5.0.0

400ad000-400c2000 rw-p 00098000 08:13 1847971  /usr/lib/libstdc++.so.5.0.0

400c2000-400c7000 rw-p 00000000 00:00 0

400d7000-400f9000 r-xp 00000000 08:13 1144751  /lib/libm.so.6

400f9000-400fa000 rw-p 00021000 08:13 1144751  /lib/libm.so.6

400fa000-40101000 r-xp 00000000 08:13 1144783  /lib/libgcc_s.so.1

40101000-40102000 rw-p 00007000 08:13 1144783  /lib/libgcc_s.so.1

40102000-40216000 r-xp 00000000 08:13 1144746  /lib/libc.so.6

40216000-4021c000 rw-p 00113000 08:13 1144746  /lib/libc.so.6

4021c000-40221000 rw-p 00000000 00:00 0

bfffe000-c0000000 rwxp fffff000 00:00 0

 

For more information on a process’ address space and the various mappings, refer to the /proc/<pid>/maps section in Chapter 3.

有關進程的地址空間和各種映射的詳細信息, 請參閱3章中的/proc/<pid>/maps部分.

6.5.2. Stack

The stack is important because it contains information about “where” in the code a process is running. The stack contains one stack “frame” for each unfinished function that called another function. This leads to a hierarchical chain or stack of function callers and callees. The functions on the stack have not finished—in other words, each function will continue if the function they called finishes. A “stack trace” or “back trace” is the list of functions in the stack. In GDB, the backtrace or bt command will dump the stack trace for the process currently being debugged:

棧很重要, 因爲它包含正在運行的進程運行到代碼哪裏的信息。對於調用另一個函數的每個未完成函數, 棧包含一個棧 "幀"。這將導致一個分層鏈或調用函數和被調用函數的棧。棧上的函數尚未完成-換言之, 如果調用的函數完成, 則函數將繼續。"棧跟蹤" 或 "後退跟蹤" 是棧中函數的列表。在 GDB 中, backtrace或 bt 命令將轉儲當前正在調試的進程的棧跟蹤:

Code View: Scroll / Show All

(gdb) backtrace

#0 0x400d6f0b in pause () from /lib/i686/libc.so.6

#1 0x080483d0 in function4 (a=97 'a') at gdb_stack.c:9

#2 0x080483e9 in function3 (string=0xbffff340 "This is a local string")at gdb_stack.c:16

#3 0x0804843a in function2 (param=3) at gdb_stack.c:23

#4 0x08048456 in function1 (param=3) at gdb_stack.c:31

#5 0x0804847d in main () at gdb_stack.c:38

 

From this stack trace (a.k.a. back trace), we know that the main() function called function1(), and that function1() called function2(), and so forth. The last function on the stack is pause(). If pause finishes (that is, exits), function4 will continue. If function4 finishes, function3 will continue, and so on.

從這個棧跟蹤 (又稱backtrace) 中, 我們知道main () 函數調用 function1 (), 而 function1 () 調用 function2 (), 依此類推。堆棧上的最後一個函數是pause ()。如果pause完成 (即退出), function4 將繼續。如果 function4 完成, function3 將繼續, 等等。

Note that in this output the arguments, filename, and line number for all functions except pause() are shown. This is because gdb_stack.c (a little program specifically for this example) was compiled with -g to include debug symbols, but the source used to create libc.so.6, where pause is contained, was not. If the program was built without -g, we would only know the address of the program counter for each stack frame and not the line of code.

請注意, 在此輸出中顯示除pause () 以外的所有函數的參數、文件名和行號。這是因爲 gdb_stack (專門爲本例編寫的一個小程序) 是用 -g 編譯的, 包括調試符號。 但用於創建 libc.so.6的源代碼(其中包含pause)是沒有-g編譯的。如果程序是在沒有 -g 的情況下構建的, 我們只知道每個棧幀的程序計數器的地址, 而不是代碼行。

Note: Some distributions strip their shared libraries, removing the main symbol table and other information. However, there is usually a non-stripped shared library (such as libc6-dbg.so) that contains the main symbol table and additional information useful for debugging.

注意: 一些發行版剝離了它們的共享庫, 刪除了主符號表和其他信息。但是, 通常有一個非剝離的共享庫 (如 libc6-dbg.so), 其中包含主符號表和用於調試的附加信息。

 

The function names themselves aren’t stored in each stack frame. The function names are too long, and the reality is that function names aren’t much use to the computer. Instead each stack frame contains the saved instruction pointer or program counter. The saved program counter can be translated into the function name by looking at the address of the program counter and the instruction of the library or executable that is loaded in that region of memory. The full set of steps to translate a program counter address into a line of code can be found in Chapter 4, which contains detailed information about compiling programs. More information on stack traces can be found in Chapter 5.

函數名稱本身並不存儲在每個棧幀中。函數名太長, 而且函數名對計算機沒有太大用處。相反, 每個棧幀都包含已保存的指令指針或程序計數器。通過查看程序計數器的地址以及加載在內存區域中的庫或可執行文件的指令, 可以將保存的程序計數器轉換爲函數名。將程序計數器地址轉換成一行代碼的全部步驟可以在第4章中找到, 其中包含有關編譯程序的詳細信息。有關棧跟蹤的更多信息, 可以在第5章中找到。

Let’s go back to the stack trace output to explain the format. The numbers on the left indicate the frame number for each stack frame. These numbers can be used with various GDB frame-related commands to reference a specific frame. The next column in the stack trace output is a hexadecimal address of the program counter stored for each stack frame. On x86-based hardware, shared libraries usually get loaded around address 0x40000000, and the executable gets mapped in at 0x08048000. See the /proc/<pid>/maps section in the /proc filesystem chapter (Chapter 3) for more information on address space mappings. It is good enough for this discussion to know that the program counter address of 0x400d6f0b for the function pause makes sense because it is found in a shared library and is near the starting address for shared libraries of 0x40000000. The program counter addresses starting with 0x08048 for the other functions also makes sense because it is part of the executable created from the gdb_stack.c source code.

讓我們回到棧跟蹤輸出來解釋格式。左邊的數字表示每個棧幀的幀數。這些數字可以與各種 GDB 幀相關的命令一起使用來引用特定的幀。棧跟蹤輸出中的下一列是爲每個棧幀存儲的程序計數器的十六進制地址。在 x86硬件上, 共享庫通常在地址0x40000000 上加載, 可執行文件在0x08048000 中被映射。有關地址空間映射的更多信息, 請參見/proc文件系統章節 (第3 章) 中的/prco/<pid>/maps部分。通過討論知道0x400d6f0b 的程序計數器地址的函數pause是有意義的, 因爲它是在共享庫中找到的, 並接近0x40000000 共享庫的起始地址。對於其他函數, 從0x08048 開始的程序計數器地址也很有意義, 因爲它是從 gdb_stack.c 創建的可執行文件的一部分.

Use the bt full command to see more information in the stack trace including a dumping of local variables for each frame:

使用 bt 完整命令可以查看棧跟蹤中的更多信息, 包括爲每個幀的局部變量:

(gdb) bt full

#0  0x400d6f0b in pause () from /lib/i686/libc.so.6

No symbol table info available.

#1  0x080483d0 in function4 (a=97 'a') at gdb_stack.c:9

       b = 1

#2  0x080483e9 in function3 (string=0xbffff340 "This is a local string")

   at gdb_stack.c:16

       a = 97 'a'

#3  0x0804843a in function2 (param=3) at gdb_stack.c:23

       string = "This is a local string"

#4  0x08048456 in function1 (param=3) at gdb_stack.c:31

       localVar = 99

#5  0x0804847d in main () at gdb_stack.c:38

       stackVar = 3

(gdb)

 

This will include the local variables for each stack frame that has debug information. Usually, though, you’ll want to see specific local variables and just print them as needed. See the following section on printing values and variables.

這將包括每個具有調試信息的棧幀的局部變量。通常, 您需要查看特定的局部變量, 然後根據需要打印它們。有關打印值和變量,請參見下一節。

6.5.2.1. Navigating Stack Frames

GDB can only see local variables that belong to the current frame (that is, in the “scope” of the current function). If you want to view a local variable that is part of another function in the stack, you must tell GDB to switch its focus to that frame. You may also want to change the current stack frame (that is, function) to perform other operations in that scope. For example, you can tell GDB to “finish” a function, and GDB will run the process until the current function (at the current stack frame) finishes and returns control to the function that called it.

GDB 只能看到屬於當前幀的局部變量 (即當前函數的 "範圍")。如果要查看作爲棧中另一個函數的一部分的局部變量, 則必須告訴 GDB 將其焦點切換到該幀。您可能還希望更改當前棧幀 (即函數), 以便在該範圍內執行其他操作。例如, 您可以告訴 gdb "完成" 一個函數, gdb 將運行該進程, 直到當前函數 (在當前棧幀) 完成, 並將控制權返回給調用它的函數。

Unless any previous frame navigation has been performed, you will always be in frame #0 to start. This is always the “top” frame on the stack. The quickest way to switch stack frames is to use the frame command with the specific frame number.

除非以前處理過其它幀, 否則您將始終處於幀 #0 開始。這始終是棧上的 "頂部" 幀。切換棧幀的最快方法是使用具有特定幀數的幀命令。

(gdb) frame 3

#3  0x0804843a in function2 (param=3) at gdb_stack.c:23

23         function3( string );

(gdb)

 

You can also use the up and down commands to walk up and down frames in the stack:

也可以使用up和down命令在棧中向上和向下遍歷幀:

(gdb) up

#1 0x080483d0 in function4 (a=97 'a') at gdb_stack.c:9

9     pause();

(gdb) down

#0 0x400d6f0b in pause () from /lib/i686/libc.so.6

(gdb) down

Bottom (i.e., innermost) frame selected; you cannot go down.

(gdb) up

#1 0x080483d0 in function4 (a=97 'a') at gdb_stack.c:9

9     pause();

(gdb) up

#2 0x080483e9 in function3 (string=0xbffff340 "This is a local string")

  at gdb_stack.c:16

16     function4( a );

(gdb) up

#3 0x0804843a in function2 (param=3) at gdb_stack.c:23

23     function3( string );

(gdb)

 

GDB won’t let you go past the beginning or the end of the stack, so you can use up and down without concern.

GDB 不會讓你越過棧的範圍, 所以你可以使用up/down而不關心越界。

Note: Stacks grow downward toward smaller addresses on x86-based hardware, so the “bottom” of the stack will have the highest stack frame address. The top of the stack is the stack frame in GDB that has a frame number of 0 (zero) and will have the lowest numbered address in memory. Please also note that there is a diagram of stack traces in Chapter 5.

注意: 棧向下擴展到 x86硬件上較小的地址, 因此棧的 "底部" 將具有最高的棧幀地址。棧的頂部是 GDB 中的棧幀, 它的幀數爲 0 (零), 並將在內存中具有最低編號的地址。還請注意, 在第5章中有一個棧跟蹤圖。

6.5.2.2. Obtaining and Understanding Frame Information

Sometimes you’ll want/need to get more information about a stack frame. For example, a stack frame contains information about function arguments, local variables, and some interesting registers. This can be particularly useful if you don’t have the source code for the application. To get more information on a particular stack frame, use the info frame command:

有時, 您需要獲取有關棧幀的更多信息。例如, 棧幀包含有關函數參數、局部變量和一些有趣的寄存器的信息。如果沒有應用程序的源代碼, 這可能特別有用。要獲取有關特定堆棧幀的更多信息, 請使用 "info frame" 命令:

(gdb) info frame 2

Stack frame at 0xbffff330:

 eip = 0x80483e9 in function3 (gdb_stack.c:16); saved eip 0x804843a

 called by frame at 0xbffff370, caller of frame at 0xbffff310

 source language c.

 Arglist at 0xbffff328, args: string=0xbffff340 "This is a local string"

 Locals at 0xbffff328, Previous frame's sp is 0xbffff330

 Saved registers:

 ebp at 0xbffff328, eip at 0xbffff32c

(gdb)

 

There’s a lot of information here, so let’s break it down a little.

這裏有很多信息, 讓我們把它分解一下。

Stack frame at 0xbffff330:

 

This is simply the address of the stack frame.

這只是棧幀的地址。

eip = 0x80483e9 in function3 (gdb_stack.c:16); saved eip 0x804843a

 

The eip (Extended Instruction Pointer) address points to the next instruction to be executed in this frame. We can then see that this frame is associated with function3(). Because this source code was compiled with debug symbols, the source file name and line number is also displayed. The saved eip is the address that points to the next instruction to be executed in the previous frame in the stack. So for example, if we look at the information for the previous frame in the stack, its eip will be this stack’s saved eip.

eip (擴展指令指針) 地址指向要在此幀中執行的下一個指令。然後, 我們可以看到此幀與 function3 () 相關聯。由於此源代碼是用調試符號編譯的, 因此也會顯示源文件名和行號。保存的 eip 是指向要在棧中的上一個幀中執行的下一個指令的地址。例如, 如果我們查看棧中上一個幀的信息, 它的 eip 將是這個棧保存的 eip。

called by frame at 0xbffff370, caller of frame at 0xbffff310

 

called by frame indicates the address of the frame that called this frame. caller of frame indicates which frame the current frame calls. So the called by and caller of basically display the addresses of the two frames that surround the current frame.

所謂按幀指示調用此幀的幀的地址。幀的調用方指示當前幀調用的幀。因此, 調用方和被調用方基本上顯示了環繞當前幀的兩個幀的地址。

source language c.

 

This line tells us the language in which the program was written.

此行告訴我們編寫程序的語言。

Arglist at 0xbffff328, args: string=0xbffff340 "This is a local string"

 

Arglist indicates the address in which the local function variables start. args: displays the arguments passed to this frame. Because this code was compiled with debug symbols, we can see the symbolic name of the argument, string. The address of this variable is also displayed. Note that since this particular variable is itself a local variable, the address appears in the stack frame that called the current frame.

參數列表指示本地函數變量開始的地址。參數: 顯示傳遞到此幀的變量。因爲此代碼是用調試符號編譯的, 所以我們可以看到參數的符號名稱, 字符串。此變量的地址也會顯示出來。請注意, 由於此特定變量本身是局部變量, 因此地址將出現在調用當前幀的棧幀中。

Locals at 0xbffff328, Previous frame's sp is 0xbffff330

 

Locals displays the address in which the local variables start. Previous frame’s sp displays stack pointer of the previous frame.

本地變量顯示了局部變量的起始地址。上一個幀的 sp 顯示上一幀的棧指針。

Saved registers:

ebp at 0xbffff328, eip at 0xbffff32c

 

This line displays the values of the ebp and eip registers in the current frame. The eip register is the instruction pointer, and the ebp pointer is the stack base pointer. For more information on these registers and the stack layout, refer to Chapter 5.

此行顯示當前幀中 ebp 和 eip 寄存器的值。eip 寄存器是指令指針, ebp 指針是棧基指針。有關這些寄存器和棧佈局的詳細信息, 請參閱第5章。

6.5.3. Examining Memory and Variables

Besides looking at the stack, looking at the contents of memory and variables is probably the next most useful feature of a debugger. In fact, you will spend most of your time in a debugger looking at variables and/or the contents of memory trying to understand what is going wrong with a process.

除了查看棧之外, 查看內存和變量的內容可能是調試器的下一個最有用的功能。實際上, 您將花費大部分時間在調試器中查看變量和/或內存的內容, 試圖瞭解進程的錯誤。

6.5.3.1. Variables and Scope and Type

Variables in a C/C++ program have different scope depending on how they were declared. A global variable is a variable that is defined to be externally visible all of the time. A static variable can be declared in the scope of a file or in a function. Static variables are not visible externally and are treated in a special way. An automatic variable is one that is declared inside a function and is only available on the stack while the corresponding function is running and has not finished. Here is a quick overview of how to declare the variables with the three scopes (this is important to understand how to handle each type in GDB).

c/c++ 程序中的變量根據聲明的方式有不同的範圍。全局變量是定義爲在所有時間外部可見的變量。靜態變量可以在文件內或函數中聲明。靜態變量在外部不可見, 並以特殊方式進行處理。自動變量是在函數內聲明的, 並且在相應的函數正在運行且尚未完成時,僅在棧上可用。下面是如何用三個作用域聲明變量的快速概述 (這對於瞭解如何處理 GDB 中的每種類型很重要)。

                                                                 Global variable:

int foo = 6 ;

Note: You must declare the variable at the highest scope and outside of any function.

注意: 必須在最大範圍和任何函數的外部聲明變量。

 

                                                                 Static variable:

static int foo = 6 ;

 

                                                                 Automatic variable:

int function1()

{

  int foo = 6 ;

 

}

 

Global symbols are always available to view in a debugger, although you might not always know the type. Static variables are also always available, but a stripped executable or library may not include the names of the static functions. Function local variables (also known as automatic variables) are only available for printing if the source code is compiled with -g. Building in debug mode provides two things necessary for printing automatic variables. The first is the type information. The second is the debug information for automatic variables, which includes linking them to the type information.

全局符號始終可以在調試器中查看, 儘管您不知道全局變量的類型。靜態變量也始終可用, 但剝離的可執行文件或庫可能不包括靜態函數的名稱。函數局部變量 (也稱爲自動變量) 只有在用-g編譯源代碼時纔可以打印出來. 在調試模式下編譯提供了打印自動變量所需的兩個條件。第一個是類型信息。第二個是自動變量的調試信息, 調試信息將自動變量鏈接到類型信息。

Consider a global variable defined as follows:

請考慮如下定義的全局變量:

const char *constString = "This is a constant string!";

 

Printing this from inside GDB for a program that was not compiled with -g will produce the following:

從不是用 -g 編譯的程序的GDB 內部打印這個程序,將生成以下內容:

(gdb) print constString

$2 = 134513832

 

GDB can find the global variable, but it does not know its type. As long as this is a base type (that is, not a structure, class, or union), we can still print this properly using the print formatting capabilities:

GDB 可以找到全局變量, 但它不知道它的類型。只要這是基類型 (即不是結構體、類或聯合), 我們仍然可以格式功能正確地打印此內容:

(gdb) printf "%s\n", constString

This is a constant string!

(gdb)

 

We were able to print constString as a string because it is a base type and is a global symbol. A local symbol would not be stored in the symbol table and would not reference it without building in debug.

我們能夠打印 constString 爲字符串, 因爲它是一個基類型, 是一個全局符號。本地符號不會存儲在符號表中, 並且只有在調試模式中, 纔會引用它。

Next, let’s take a look at how to print a static variable. This is similar to a global variable except that there may be more than one static variable with the same name.

接下來, 讓我們來看看如何打印靜態變量。這與全局變量類似, 只是可能有多個同名的靜態變量。

Consider the static function declared at the file level (that is, declared static but outside the scope of a function) as:

考慮在文件級別聲明的靜態變量 (即聲明爲靜態的, 但在函數的範圍之外), 如:

static int staticInt = 5 ;

 

Next, let’s find out how many static functions there are:

接下來, 讓我們來看看有多少靜態變量:

(gdb) info variable staticInt

All variables matching regular expression "staticInt":

 

Non-debugging symbols:

0x08049544 staticInt

 

Because only one is listed, there is only one. Next, let’s print its value:

因爲只有一個被列出, 所以只有一個。接下來, 讓我們打印它的值:

(gdb) print /x staticInt

$3 = 0x5

(gdb)

 

Automatic variables are not stored in the process symbol table, meaning that without compiling in -g, automatic variables have no name in the compiled code. Consider the following simple function that defines an integer b:

自動變量不存儲在進程符號表中, 這意味着在沒有 –g 編譯的情況下, 自動變量在編譯後的代碼中沒有名稱。請考慮以下定義整數 b 的簡單函數:

int foo( int a )

{

  int b = 0 ;

 

...

}

 

If we compile this without debug (that is, without -g), GDB will have no information about this variable name or type.

如果我們沒有調試信息編譯這個(即, 沒有 -g)程序時, GDB 將沒有關於這個變量名稱或類型的信息。

g++ foo.C -o foo

 

And now in GDB (skipping the first part of the session for clarity)

現在在 GDB (爲了清晰起見,跳過會話的第一部分)

(gdb) break foo

Breakpoint 1 at 0x8048422

(gdb) run

Starting program: /home/wilding/src/Linuxbook/foo

 

Breakpoint 1, 0x08048422 in foo(int) ()

(gdb) print b

No symbol "b" in current context.

 

Notice how GDB cannot find any information about the variable at all. This is one of the reasons it is much easier to use GDB when the program is compiled with -g. If the program was built with -g, GDB would be able to find the variable and print its value:

請注意, GDB 根本找不到有關該變量的任何信息。這是與 –g進行程序編譯時,更容易使用 GDB的一個原因。如果程序是用 -g 構建的, GDB 將能夠找到變量並打印其值:

(gdb) break foo

Breakpoint 1 at 0x8048422: file foo.C, line 30.

(gdb) run

Starting program: /home/wilding/src/Linuxbook/foo

 

Breakpoint 1, foo(int) (a=6) at foo.C:30

30     int b = 0 ;

(gdb) print b

$1 = 1075948688

 

Examining memory and values of variables is another very important aspect of debugging. Most programs allocate memory using malloc or new (the latter for C++) to store variables. Variables that are stored in a heap (such as the one used by malloc or heap), do not have a symbol table, and the compiler does not create any link or any type information for such variables. Such variables do not really have a “scope” and need to be handled specially, as outlined in the section, “Viewing Data in Memory.”

檢查變量的內存和值是調試的另一個非常重要的方面。大多數程序使用 malloc 或 new (new用於 c++) 分配內存來存儲變量。存儲在堆中的變量 (例如, 由 malloc 或堆使用的) 沒有符號表, 編譯器不會爲這些變量創建任何鏈接或任何類型信息。這些變量實際上沒有 "範圍", 需要特別處理, 如 "在內存中查看數據" 一節中所述。

6.5.3.2. Print Formatting

Print formatting allows users to change how variables and memory are displayed. For example, sometimes it is more useful to print an integer in hexadecimal format, and other times it helps to see it in decimal format. The most basic way (that is, without formatting) to see the value of a variable is with the print <variable_name> command.

打印格式允許用戶更改變量和內存的顯示方式。例如, 有時用十六進制格式打印整數更有用, 而其他時侯則可以用十進制格式來查看它。查看變量值的最基本方法 (即不帶格式) 使用print <variable_name>命令.

(gdb) print stackVar

$1 = 10

 

You can specify what format you want print to display in. For example, to see it in hex, use the /x argument:

可以指定打印格式。例如, 若要以十六進制查看它, 請使用-x 參數:

(gdb) print /x stackVar

$2 = 0xa

 

Notice how the value of the variable is always preceded by a dollar sign with a number followed by an equal sign. This is because GDB is automatically assigning and storing the value printed to an internal variable of that name. GDB calls these convenience variables. So for example, you can later reuse the newly created $1 variable in other calculations in this debugging session:

請注意, 變量的值總是在前面加上一個美元符號, 後跟一個數字和一個等號。這是因爲 GDB 自動分配和存儲打印到該名稱的內部變量的值。GDB 調用這些變量。例如, 您以後可以在該調試會話的其他計算中重用新創建的$1變量:

(gdb) print $1 + 5

$3 = 15

 

A more powerful alternative to the print command is the printf command. The printf command works much the same as the standard C library function works:

print命令的一個更強大的替代方法是 printf 命令。printf 命令的工作原理與標準 C 庫函數的工作原理相同:

(gdb) printf "The value of stackVar in hex is 0x%x\n", stackVar

The value of stackVar in hex is 0xa

 

Printing an array is just as easy. Consider the following array:

int list[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } ;

 

You can print this array in its entirety or just one element:

(gdb) print list

$2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

 (gdb) print list[3]

$1 = 3

 (gdb)

 

Notice, though, that the array indexing is C-style with 0 (zero) being the first index value. Thus, the element whose index is 3 is actually the fourth element of the array.

不過, 請注意, 數組索引是 C 風格, 0 (零) 是第一個索引值。因此, 索引爲3的元素實際上是數組的第四個元素。

In some cases, you may want to simulate an array with the @ character. Consider the following integer:

在某些情況下, 您可能希望模擬帶有 @ 字符的數組。請考慮以下整數:

int globInt = 5 ;

 

We can treat this integer as an array that is three elements in length:

我們可以將此整數視爲長度爲三個元素的數組:

(gdb) print globInt@3

$16 = {5, 5, 134514112}

 

This is usually useful when dealing with memory that has been allocated in a heap and does not have any specific type according to GDB.

這在處理在堆中分配的內存時通常很有用, 並且 GDB 中沒有任何特定類型。

6.5.3.3. Determining the Type of Variable

Occasionally, you’ll want to figure out what kind of variable you’re about to look at. This is mostly useful when looking at global and static variables. When needed, use the whatis command:

有時候, 你會想知道你要看的是什麼樣的變量。這在查看全局和靜態變量時是非常有用的。需要時, 使用 whatis 命令:

(gdb) whatis a

type = int

6.5.3.4. Viewing Data in Memory

As mentioned earlier, if a problem is not compiled with -g, it won’t have any of the debug information such as variable type information. In this case, you’ll probably need to look at memory directly using the examine or x command. This command accepts a number of arguments—/FMT ADDRESS. FMT is a repeat count followed by a format letter and a size letter. ADDRESS specifies the address at which to start the display of data. The following example will dump eight 4-byte hex values starting at 0x08048000:

如前所述, 如果沒有使用-g 編譯問題, 它將不會有任何調試信息 (如變量類型信息)。在這種情況下, 您可能需要使用 “examine” 或 "x" 命令直接查看內存。此命令接受許多參數-/FMT ADDRESS。FMT是重複計數, 後跟格式字母和大小字母。ADDRESS指定開始顯示數據的地址。下面的示例將從0x08048000 開始轉儲八個4字節十六進制值:

(gdb) x /8xw 0x08048000

0x8048000:   0x464c457f  0x00010101  0x00000000  0x00000000

0x8048010:   0x00030002  0x00000001  0x08048320  0x00000034

 

The GDB help output for constructing an x command is very useful, but for convenience, the most commonly used format codes are shown in the following table.

用於構造 x 命令的 GDB 幫助輸出非常有用, 但爲了方便起見, 下表顯示了最常用的格式代碼。

Table 6.1. GDB Format Codes.

Format Description

Format Code

1-byte ASCII character

cb

2-byte decimal integer

dh

4-byte decimal integer

dw

1-byte hexadecimal number

xb

2-byte hexadecimal number

xh

4-byte hexadecimal number

xw

8-byte hexadecimal number

xg

String

s

6.5.3.5. Formatting Values in Memory

If you have a variable in memory and you know the address, you can use casting to print with the correct formatting. Consider the string defined in the preceding example constString. Here are four different methods to print this variable correctly as a string.

如果內存中有變量, 並且知道地址, 則可以使用強制類型轉換以正確的格式打印出來。以前面的constString 中定義的字符串爲例。下面是四種不同的方法, 可以正確地將該變量打印爲字符串。

(gdb) print constString

$5 = 0x80485c0 "This is a constant string!"

  1.   x /s 0x080485c0

0x80485c0 <_IO_stdin_used+28>:   "This is a constant string!"

 (gdb) printf "%s\n",0x080485c0

This is a constant string!

(gdb) print (char *) 0x080485c0

$6 = 0x80485c0 "This is a constant string!"

 

The first requires the shared library or executable that contains this variable to be built with debug information (compiled with -g). Without this debug information, the debugger will not have type information for the symbol constString. The second method uses the GDB examine command (x) with a string format (/s). The third method uses the GDB printf command with a string token (%s). The last method uses a casting feature in GDB to print an address as a specific type. The last three methods do not require any type information (that is, they will work on a program that is not compiled with -g).

第一個要求使用包含此變量的共享庫或可執行文件以調試信息生成 (用-g 編譯)。如果沒有此調試信息, 調試器將不具有符號 constString 的類型信息。第二種方法使用 GDB 檢查命令 (x) 與字符串格式 (/s)。第三種方法使用 GDB printf 命令和一個字符串標記 (%s)。最後一種方法使用 GDB 中的強制類型轉換功能將地址打印爲特定類型。最後三方法不需要任何類型信息 (即, 它們將處理未使用-g 編譯的程序)。

The last method is interesting because it uses a C-style cast to tell GDB how to print it. This will work with any type, which makes this very useful when debugging values that are in memory but do not have any direct type information. For example, an application may use malloc or other memory allocation function to get memory to store a variable with a specific type. The compiler will include type information for the variable but will not link it with the memory that was allocated via malloc.

最後一個方法很有趣, 因爲它使用 C 風格的強制類型轉換來告訴 GDB 如何打印它。這將適用於任何類型, 這使得在調試內存中但沒有任何直接類型信息的值時非常有用。例如, 應用程序可以使用 malloc 或其他內存分配函數獲取內存以存儲具有特定類型的變量。編譯器將包括變量的類型信息, 但不會將其與通過 malloc 分配的內存相鏈接。

The cast formatting method is probably the most powerful method of formatting complex types in GDB. We could even use the address of the constant string constString and cast it to a C++ class type in order to print that region of memory as a C++ object.

轉換格式方法可能是 GDB 中格式化複雜類型的最有效方法。我們甚至可以使用常量字符串 constString 的地址, 並將其轉換爲 c++ 類類型, 以便將該內存區域作爲 c++ 對象打印出來。

class myClass

{

  public:

 

  int myVar ;

 

  myClass() {

   myVar = 5 ;

  }

 

};

 

See the section later in this chapter titled “Finding the Address of Variables and Functions” for information on how to find the address of variables like constString. Now we can print the region of memory for the constant string as this class (note that 0x080485c0 is the address of constString):

請參見本章後面的部分, 標題爲 "查找變量和函數的地址", 以瞭解有關如何查找諸如 constString 之類的變量的地址的信息。現在, 我們可以將常量字符串的內存區域打印爲類 (請注意, 0x080485c0 是 constString 的地址):

(gdb) print (myClass) *0x080485c0

$26 = {myVar = 1936287828}

 

Of course, this was a completely invalid address, but it shows just how flexible this cast formatting is! Just to prove the point further, though, the value of the member variable myVar is 1936287828 in decimal format and 0x73696854 in hex format which, when translated to a string is sihT or This in reverse (see the man page for ascii for the ascii table). Why is this in reverse? The answer is that 32-bit x86 platforms are “little endian,” as explained later in this chapter.

當然, 這是一個完全無效的地址, 但它顯示了這種轉換格式是多麼靈活!不過, 爲了進一步證明這一點, 成員變量 myVar 的值爲十進制格式的 1936287828, 十六進制格式0x73696854, 當轉換爲字符串時 sihT 或反轉 (請參見 ascii 表 ascii 的幫助手冊)。爲什麼會反過來呢?答案是, 32 位 x86 平臺是 "little endian", 正如本章後面所解釋的那樣。

6.5.3.6. Changing Variables

GDB also allows you to make any changes you wish to variables and registers. To set the value of a variable, use the set variable command, or simply set:

GDB 還允許您對變量和寄存器進行更改。若要設置變量的值, 請使用 set 變量命令, 或者簡單地set:

(gdb) set variable a=5

 

or

(gdb) set a=5

(gdb) print a

$1 = 5

 

You can set the value of a register by referencing it the same way you reference it when printing its value:

您可以通過引用與在打印其值時引用它的同樣的方式來設置寄存器的值:

(gdb) set $eax=1

(gdb) print $eax

$1 = 1

Warning: Changing the value of a register without understanding what it is for will cause unpredictable behavior.

警告: 更改寄存器的值而不理解它的作用, 將導致不可預知的行爲。

6.5.4. Register Dump

Examining the contents of the registers in a live debugging session may be necessary to diagnose complex problems, especially when you don’t have the source code. Looking at a raw register dump can be like looking at a wall of hieroglyphics if you don’t have experience with assembly language on the platform you are using. The contents of the registers make sense only when you examine and understand the assembly instructions (the human readable format of machine instructions) that are using the registers. Assembly instructions directly manipulate the memory and registers of a computer. When debugging a program that has been compiled with debug symbols enabled (-g), looking at register contents is usually not necessary. However, when debugging a program that has no debug symbols, you are forced to work at the assembly level.

The command used to see a register dump in GDB is “info registers” as shown here:

在實時調試會話中檢查寄存器的內容可能對診斷複雜問題很必要, 尤其是在沒有源代碼的情況下。如果你沒有在平臺上使用彙編語言的經驗, 那麼看一個原始寄存器轉儲就像看一堵象形文字牆一樣。只有當您檢查和理解使用寄存器的彙編指令 (可讀的機器指令) 時, 寄存器的內容纔有意義。彙編指令直接操作計算機的內存和寄存器。在調試已啓用調試符號 (-g) 編譯的程序時, 通常不需要查看寄存器內容。但是, 在調試沒有調試符號的程序時, 必須在彙編程序級別上工作。用於在 GDB 中查看寄存器轉儲的命令是 "info registers", 如下所示:

(gdb) info registers

eax      0x6   6

ecx      0x1   1

edx      0x4015c490     1075168400

ebx      0x4015afd8     1075163096

esp      0xbffff3a0     0xbffff3a0

ebp      0xbffff3a8     0xbffff3a8

esi      0x40018420     1073841184

edi      0xbffff3f4     -1073744908

eip      0x8048340     0x8048340

eflags     0x200386  2098054

cs       0x23   35

ss       0x2b   43

ds       0x2b   43

es       0x2b   43

fs       0x0   0

gs       0x0   0

(gdb)

 

We know from the procedure calling conventions on x86 (see the “Procedure Calling Conventions” section in Chapter 5 for more information) that eax is used to store the return value from a function call. We don’t know for sure, but a possibility here is that a function will return the value of 6. However, without seeing the previously executed assembly instructions, we really don’t know what has been executed to bring the registers to the state we see above. However, there is some interesting information that we can pick out of this dump.

我們從 x86 的過程調用約定 (參見第5章中的 "過程調用約定" 一節中瞭解更多信息), eax 用於存儲從函數調用返回值。我們不確定, 但這裏的一個可能性是函數將返回值6。但是, 如果沒有看到以前執行的彙編指令, 我們真的不知道已經執行了哪些操作,將寄存器帶到我們上面看到的狀態。然而, 但是,我們可以從這個轉儲中挑選出一些有趣的信息。

The eip register is the instruction pointer (a.k.a program counter) and always contains the address of the current instruction in memory that will be executed. Memory addresses that are close to the eip above of 0x8048340 will become familiar the more debugging and problem determination you do on Linux for 32-bit x86 hardware. Executables always get mapped into a process’ address space at 0x08048000 on this hardware, and so instructions in this range are very common. Refer to the /proc/<pid>/maps section in Chapter 3 for more information on a process’ address space.

eip 寄存器是指令指針 (即程序計數器), 並且始終包含在內存中將執行的當前指令的地址。靠近0x8048340 上面的 eip 的內存地址將變得熟悉, 您在 Linux 上爲32位 x86 硬件進行的越多的調試和問題確定,就對此越熟悉。可執行文件始終被映射到0x08048000 上的進程地址空間, 因此此範圍內的指令非常常見。有關進程地址空間的詳細信息, 請參閱3章中的/proc/<pid>/maps部分.

One final observation we can make from the register dump has to do with the values stored in the ebp and esp registers. Addresses near 0xbffff3a0 and 0xbffff3a8 for the registers ebp and esp will also become familiar as you become more accustomed to the address space layout. The ebp and esp registers are used to control the stack and the stack segment on 32-bit x86 hardware and are usually located around the 0xbfffffff address range.

我們從寄存器轉儲中進行的最後一個觀察與存儲在 ebp 和 esp 寄存器中的值有關。0xbffff3a0 和0xbffff3a8 附近的地址對於寄存器 ebp 和 esp 也將變得熟悉, 因爲您變得更習慣於地址空間佈局。ebp 和 esp 寄存器用於在32位 x86 硬件上控制棧和棧段, 並且通常位於0xbfffffff 地址範圍附近。

Note: The meaning of each register is well beyond the scope of this chapter and will not be covered here (http://linuxassembly.org/ may be a good reference for the interested reader).

注: 每個寄存器的含義遠遠超出本章的範圍, 不會在此處涵蓋 (感興趣的讀者可以參考http://linuxassembly.org/)。

 

If you do end up looking at a register dump, it will probably be for a trap that occurred for a process that was not built with -g. In this case, you’ll probably look at the instruction that trapped and try to understand why an address that is stored in a register used by that instruction is invalid. If your program is getting a segmentation violation (SIGSEGV), it is very likely that the trapped instruction is dereferencing a bad pointer. For example, an address of 0x6 or 0x0 is outside the range of any memory segment and will result in a segmentation violation.

如果最後看到的是寄存器轉儲, 則可能是針對未使用 -g 生成的進程所發生的陷阱。在這種情況下, 您可能會查看被捕獲的指令, 並嘗試瞭解該指令所使用的寄存器中存儲的地址是否無效。如果程序正在獲得分段衝突 (SIGSEGV), 則很可能是被捕獲的指令取消引用了錯誤的指針。例如, 0x6 或0x0 的地址超出了內存段的範圍, 並將導致分段衝突。

64-bit computing is becoming more and more mainstream, and it is worth covering some of the differences between 32-bit and 64-bit as far as registers are concerned. The following dump was performed on the x86-64 architecture and was done at the exact same point in a test program as the preceding dump:

64位計算正變得越來越主流,就寄存器而言,值得涵蓋32位和64位之間的一些差異。以下轉儲是在x86-64體系結構上執行的,並且在測試程序中與前面的轉儲完全相同的時間點完成:

Code View: Scroll / Show All

(gdb) info registers

rax      0x6   6

rbx      0x7fbfffea48    548682066504

rcx      0x40000300     1073742592

rdx      0x7fbfffea58    548682066520

rsi      0x4   4

rdi      0x2   2

rbp      0x7fbfffe9e0    0x7fbfffe9e0

rsp      0x7fbfffe9d0    0x7fbfffe9d0

r8       0x40000488     1073742984

r9       0x2a955604d0    182894068944

r10      0x0   0

r11      0x2a956a2d40    182895390016

r12      0x40000300     1073742592

r13      0x1   1

r14      0x2a95879308    182897316616

r15      0x4000041a     1073742874

rip      0x4000043b     0x4000043b <main+33>

eflags     0x306  774

ds       0x33   51

es       0x2b   43

fs       0x0   0

gs       0x0   0

fctrl    0x0    0

fstat    0x0    0

ftag     0x0    0

fiseg    0x0    0

fioff    0x0    0

foseg    0x0    0

fooff    0x0    0

fop     0x0    0

xmm0     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm1     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm2     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm3     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm4     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm5     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm6     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm7     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm8     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm9     {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm10    {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm11    {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm12    {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm13    {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm14    {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

xmm15    {f = {0x0, 0x0, 0x0, 0x0}}     {f = {0, 0, 0, 0}}

mxcsr    0x1f80 8064

(gdb)

 

Note the number of registers and their different names. On different architectures the available registers and their naming will be different. Notice also that many of the values contained in the registers are much larger than the maximum 32-bit value of 0xffffffff.

請注意寄存器的數量及其不同的名稱。在不同的體系結構中, 可用寄存器及其命名將有所不同。還要注意, 寄存器中包含的許多值遠遠大於32位的最大值0xffffffff。

To see the complete listing of registers including all floating point and extended registers, use the info all-registers GDB command.

要查看所有浮點和擴展寄存器在內的寄存器的完整列表, 請使用 "info all-registers” GDB 命令。

Last, you can display the value of individual registers by referring to them by name with a $ prepended:

最後, 您可以通過按名稱(前綴$)訪問這些寄存器的來顯示各個寄存器的值:

(gdb) print $eax

$1 = 0

 

Most of the time, you’ll just need to know the value of a particular register as it is used by the assembly language. For more information on the register conventions for each hardware platform, refer to the corresponding vendor documentation. Each hardware platform will have different registers and assembly language.

大多數情況下, 您只需要知道特定寄存器在彙編語言使用時的值。有關每個硬件平臺的寄存器約定的詳細信息, 請參閱相應的供應商文檔。每個硬件平臺都有不同的寄存器和彙編語言。

Note: Okay, so far in this chapter we’ve covered a lot of basics such as attaching to a process, looking at data, displaying register values, and so on. As we get further into this chapter, the focus will shift more and more from usage to examples. The next section is where the transition starts.

注意: 好的, 到目前爲止, 在本章中我們已經討論了很多基礎知識, 例如附加到一個進程、查看數據、顯示寄存器值等等。隨着本章的深入, 焦點將越來越多地從用法轉移到示例。下一節是轉換開始的位置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章