6.9. Tips and Tricks

6.9. Tips and Tricks

This next section covers some useful tips and tricks for more advanced debugging. The tips include more advanced ways to attach to a process at a particular point in time, methods to find the address of variables and functions, and a useful technique for viewing data structures fully formatted for an executable and/or library that was not built in debug mode (that is, with -g).

下一节将介绍一些有用的提示和技巧, 以进行更高级的调试。这些提示包括在特定时间点附加到进程的更高级方法、查找变量和函数地址的方法, 以及查看完全格式化为未内置的可执行文件和/或没有在调试模式(即使用 -g)的库的数据结构的有用技术。

6.9.1. Attaching to a Process—Revisited

6.9.1.1. The pause() method

Sometimes you run into a situation where you need to get a process into GDB under very specific conditions. For example, you might be investigating a problem that disappears when you compile the program with -g. In this case, you won’t be able to set a breakpoint at a file name and line of code. Because there is no debug information, you might need/ want to add some special code into the program to set a manual type of breakpoint.

有时你遇到了一种情况, 你需要在非常特殊的条件下, 在 GDB 中得到一个进程。例如, 您可能正在调查与 -g 一起编译程序时消失的问题。在这种情况下, 您将无法在文件名和代码行上设置断点。因为没有调试信息, 所以您可能需要/希望在程序中添加一些特殊代码来设置手动类型的断点。

There are three good ways to stop a program at a specific point and attach a debugger:

有三种好方法可以在特定点停止程序并附加调试器:

  1. Use the pause function.
  2. Use a for or while loop.
  3. Use the system() function to spawn an xterm.

The pause() function will cause the process to wait until it receives a signal (see the pause(2) man page for more information). Unfortunately, GDB occasionally has difficulty in properly handling situations like this, so a simple trick may be needed to ensure it works properly. Consider the following code:

pause () 函数将导致进程等待, 直到接收到信号 (有关详细信息, 请参阅pause (2)帮助手册)。不幸的是, GDB 偶尔会在正确处理这样的情况时遇到困难, 因此可能需要一个简单的技巧来确保它能正常工作。请考虑以下代码:

#include <stdio.h>

#include <unistd.h>

 

int main( void )

{

   int foo = 1;

 

   printf( "my pid = %d\n", getpid() );

 

   pause();

 

   printf( "got signal, resuming!\n" );

 

   return( 0 );

}

 

Compiling and running this program causes it to display its pid then wait for a signal. If we attach GDB to this process and look at the stack, we will see the following:

编译和运行此程序会导致它显示其 pid, 然后等待信号。如果我们将 GDB 附加到这个过程中并查看堆栈, 我们将看到以下内容:

(gdb) where

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

#1  0x080483d4 in main () at mypause.c:11

(gdb)

 

With debuggers on other platforms such as dbx, you could simply type “next” to go to the next instruction, and the debugger would automatically jump to the next instruction for which debug symbols existed, which in this case is in the function main(). We can tell that main() has been compiled with debug symbols because the file and line number are displayed. If we give the next command to GDB, we will see the following:

使用其他平台 (如 dbx) 上的调试器, 您可以简单地键入 "next" 转到下一条指令, 调试程序将自动跳转到带有调试符号的下一条指令, 在本例中是函数 main ()。因为文件和行号显示, 我们可以认为main () 已使用调试符号编译。如果我们给 GDB 下一个命令, 我们将看到以下内容:

(gdb) next

Single stepping until exit from function pause,

which has no line number information.

 

At this point, the GDB session will appear to be hung. In actuality, it is sitting in the pause() system call waiting for a symbol. If we use another shell to send the original process a SIGCONT, pause() will terminate, and the process will continue. Unless a breakpoint has been set at some point after the pause() call, the program will continue execution, possibly to completion.

此时, GDB 会话将显示为挂起。实际上, 它正坐在pause () 系统调用中等待信号。如果我们使用另一个 shell 发送原始进程 SIGCONT, pause () 将终止, 并且进程将继续。除非在pause () 调用后某个时刻设置了断点, 否则程序将继续执行, 最终会完成。

That requires an additional shell and kill -CONT <pid> command, which is a little tedious. An easier way is to have GDB itself send the SIGCONT. Note that a breakpoint is also set at line 13 to stop execution after pause() terminates:

这需要额外的 shell 和 kill –CONT <pid>命令, 这有点繁琐。更简单的方法是让 GDB 自己发送 SIGCONT。请注意, 还会在行13设置断点, 以在pause () 终止后停止执行:

(gdb) where

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

#1  0x080483d4 in main () at mypause.c:11

(gdb) break 13

Breakpoint 1 at 0x80483d4: file mypause.c, line 13.

(gdb) signal SIGCONT

Continuing with signal SIGCONT.

 

Breakpoint 1, main () at mypause.c:13

13         printf( "got signal, resuming!\n" );

(gdb)

 

The debugging session can now continue exactly at the location desired.

调试会话现在可以在完全所需的位置继续。

6.9.1.2. The “for” or “while” Loop Method

The “for” or “while” loop method is similar but does not require sending a signal.

"for" 或 "while" 循环方法类似, 但不需要发送信号。

#include <stdio.h>

#include <unistd.h>

 

int main( void )

{

  int foo = 1;

 

  printf( "my pid = %d\n", getpid() );

 

  while ( foo == 1 ) ;

 

  return( 0 );

}

 

penguin> g++ pause.C -o pause -g

penguin> gdb pause

 

In this case, the program will loop indefinitely unless the variable foo is changed to a different value than 1. You can use the “top” program to find this process, given it will consume a lot of CPU time and will be near the top of the display (see Appendix A for more information on top). Attaching to the process in another window, we will see that the process is stopped on this loop.

在这种情况下, 除非变量 foo 更改为不同于1的值, 否则程序将无限期循环。您可以使用 "top" 程序来查找这个过程, 因为它将消耗大量的 CPU 时间, 并且将接近显示的顶部 (请参阅附录 a 以获得有关上面的更多信息)。附加到另一个窗口中的进程, 我们将看到该进程在此循环上停止。

penguin> gdb - 15205

... (unimportant output deleted) ...

10     while ( foo == 1 ) ;

(gdb) set foo=2

(gdb) step

12     printf( "got signal, resuming!\n" );

(gdb)

 

As soon as we set the value of foo to 2, the program can pop out of the loop and continue. We used step to move forward by one line to show that the program is indeed free of this loop.

一旦我们将 foo 的值设置为 2, 程序就会跳出循环并继续。我们使用step向前移动一行, 以表明该程序确实跳出这个循环。

6.9.1.3. The xterm method

This method and the pause method work well if the program uses the terminal (the command line display), but what if the program is a daemon (that is, it runs in the background and has no terminal) or has graphical user interface? In this case, you can still get the process ID of the stopped process by spawning an xterm as in the following example:

如果程序使用terminal (命令行显示), 此方法和pause方法工作正常, 但如果程序是一个守护进程 (即, 它在后台运行且没有终端), 或者具有图形用户界面, 该怎么办?在这种情况下, 您仍然可以通过生成 xterm 来获取停止进程的进程 ID, 如下面的示例所示:

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

 

int main( void )

{

  char buf[1024] ;

 sprintf( buf, "/usr/bin/xterm -e \"/bin/echo $$ ; error\"\n", getpid() );

 

  system( buf ) ;

 

  return( 0 );

}

 

The command being run will actually cause an error on purpose to ensure that the xterm doesn’t exit after printing the process ID. The error message in the newly created xterm will be xterm: Can’t execvp /bin/echo 15634 ; error: No such file or directory, where 15634 is the process ID (and will be different if you try this method). You can also use the -title switch to xterm to change the title of the xterm to match the process ID of the program that spawned the process.

正在运行的命令实际上会导致错误, 以确保在打印进程 ID 后 xterm 不会退出。新创建的 xterm 中的错误信息将在xterm中显示: Can’t execvp /bin/echo 15634 ; error: No such file or directory , 其中15634是进程 ID (如果您尝试此方法, 进程ID将会有所不同)。您还可以使用 title 开关 xterm 更改 xterm 的标题, 以匹配生成进程的ID。

Once you attach to the process ID with GDB, simply exit the xterm to regain control of the process. It will continue as normal from that point on.

在使用 GDB 连接到进程 ID 之后, 只需退出 xterm 即可重新获得对该进程的控制权。它将从正常点上继续 。

6.9.2. Finding the Address of Variables and Functions

Processes can have dozens of libraries loaded at any given time with hundreds or thousands of global and static variables each. With this many global variables in the address space at the same time, there is a chance that two or more variables will have the same name. Worse, there is a chance that the wrong variable will be used (the ELF chapter includes more detail on this) or that you will look at the wrong variable using GDB. Fortunately, GDB provides a quick and easy method to find all variables that match a pattern (a regular expression). The following example finds all variables that have the string “environ” in their name:

进程可以在任何给定的时间加载大量的库, 每一个库都有成百上千个全局和静态变量。同时, 在地址空间中有许多全局变量, 有可能两个或多个变量同名。更糟糕的是, 有可能会使用错误的变量 (ELF 章节包含更多细节), 或者您将使用 GDB 查看错误的变量。幸运的是, GDB 提供了一种快速简便的方法来查找与模式匹配的所有变量 (正则表达式)。下面的示例查找在其名称中具有字符串 "environ" 的所有变量:

(gdb) info variables environ

All variables matching regular expression ".*environ.*":

 

File sh.func.c:

Char **STR_environ;

char **environ;

 

File tw.init.c:

Char **STR_environ;

 

File tw.parse.c:

Char **STR_environ;

 

File sh.c:

Char **STR_environ;

char **environ;

Non-debugging symbols:

0x08096184 __environ@@GLIBC_2.0

0x08096184 environ@@GLIBC_2.0

0x401acb38 __environ

0x401acb38 _environ

0x401b1c34 last_environ

0x400129d8 __environ

0x400129d8 _environ

 

There are several variables that have this string (“environ”) and a few that have exactly the same name (“_environ”). The address of each variable is listed on the left for the variables that do not have debug information.

有几个变量有这个字符串 ("environ") 和一些具有完全相同的名称 ("_environ")。每个变量的地址都列在左侧, 用于没有调试信息的变量。

There can also be thousands of functions in the address space, including more than one function with the same name. Using a similar method to that just shown for variables, you can get GDB to display all of the functions that match a certain pattern. Here is an example where we find all of the functions that have the string “main” in them:

地址空间中还有数以千计的函数, 其中包含多个同名的函数。使用类似的方法来显示变量, 可以让 GDB 显示与某种模式匹配的所有函数。下面是一个示例, 我们在其中找到了包含字符串 "main" 的所有函数:

Code View: Scroll / Show All

(gdb) info functions main

All functions matching regular expression ".*main.*":

 

File sh.c:

int main(int, char **);

 

Non-debugging symbols:

0x08049ca0 __libc_start_main

0x400b0400 __libc_start_main

0x400b05c0 __libc_main

0x400bce30 __bindtextdomain

0x400bce30 bindtextdomain

0x400bce60 __bind_textdomain_codeset

0x400bce60 bind_textdomain_codeset

0x400bdf10 _nl_find_domain

0x400be1e0 _nl_init_domain_conv

0x400be490 _nl_free_domain_conv

0x400be4e0 _nl_load_domain

0x400be900 _nl_unload_domain

0x400bf090 __textdomain

0x400bf090 textdomain

0x400fa4a0 _IO_switch_to_main_wget_area

0x40102410 _IO_switch_to_main_get_area

0x40107820 main_trim

0x4015de60 getdomainname

0x4015ded0 setdomainname

0x40170560 arg_trimdomain_list

0x401710a0 _res_hconf_trim_domain

0x40171130 _res_hconf_trim_domains

0x4017b4c0 nrl_domainname

0x40001990 dl_main

0x401c5dc0 xdr_domainname

0x401c6fd0 yp_get_default_domain

0x401cf670 nis_domain_of

0x401cf6b0 nis_domain_of_r

 

If you want to find a function or variable with a specific name, you can use the following regular expression ^<function or variable name>$. The carrot ^ means the beginning of the string, and the dollar sign $ means the end of the string. Here is an example of where we find all functions that have the exact name of main (Note: There is only one function with this name.):

如果要查找具有特定名称的函数或变量, 可以使用下面的正则表达式 ^<function or="" variable="" name="">$。符号 ^ 表示字符串的开头, 符号 $ 表示字符串的末尾。下面是一个示例, 其中我们找到了所有具有 main 名称的函数 (注意: 只有一个具有此名称的函数):

(gdb) info functions ^main$

All functions matching regular expression "^main$":

 

File sh.c:

int main(int, char **);

6.9.3. Viewing Structures in Executables without Debug Symbols

When debugging, particularly commercial applications, it’s very likely that the binaries have not been compiled with debug symbols (using the -g compile switch). When this happens, a knowledge of assembly, the machine’s calling conventions, and a rough idea of the data structures involved is required to fully understand what the program is doing when viewing it in a debugger. In the case of open source software, the code is readily available, so even if the executables were compiled without debug symbols, one can examine the source along with the assembly to see what is happening. In a commercial software situation where the software is having a problem on the customer’s computer, sending the source code to the customer is simply not an option for a support analyst.

在调试 (尤其是商业应用程序) 时, 很可能是二进制文件没有用调试符号 (没有使用 -g 编译开关) 编译。当发生这种情况时, 需要了解汇编、机器的调用约定以及所涉及的数据结构, 以便在调试器中查看它时, 能够完全了解该程序正在执行的操作。在开源软件的情况下, 代码是现成的, 因此即使在没有调试符号的情况下编译可执行文件, 也可以与汇编一起检查源代码, 以查看正在发生的情况。在商业软件的情况下, 软件在客户的计算机上有问题, 向客户发送源代码根本不是技术支持的选择。

For cases like these and many others, analyzing structures in raw memory as hex is certainly possible but can be very painstaking and tedious. This section introduces a trick one can use on ELF-based systems to dump data as structures from within a debugger where no debugging symbols are readily available. To demonstrate this, a very small and simple example will be used. The source code for this example is made up of one header file and one source module. The header file’s name is employee.h and contains the following code:

对于像这些和其他许多人这样的情况, 将原始内存中的结构作为十六进制分析当然是可能的, 但非常费力和乏味。本节介绍一个可以在基于 ELF 的系统上使用的窍门, 将数据从没有可用的调试符号的调试器中转储到结构。为了演示这一点, 将使用一个非常小且简单的示例。此示例的源代码由一个头文件和一个源模块组成。头文件的名称为employee.h, 包含以下代码:

struct employee_rec

{

   int employee_no;

   int is_ceo;

   char first_name[20];

   char last_name[20];

   int manager_emp_no;

   int department_no;

};

 

The source module’s name is employee.c and contains this code:

源模块的名称为employee.c 幷包含以下代码:

#include "employee.h"

 

int foo( struct employee_rec *pEmp )

{

   pEmp->manager_emp_no = 10;

   return 0;

}

 

int main( void )

{

   struct employee_rec *pEmployee;

 

  pEmployee = (struct employee_rec*)malloc( sizeof( struct employee_rec

) );

 

   /* Set the record up */

   pEmployee->employee_no = 4416;

   pEmployee->is_ceo = 0;

   strcpy( pEmployee->first_name, "Dan" );

   strcpy( pEmployee->last_name, "Behman" );

   pEmployee->manager_emp_no = 3278;

   pEmployee->department_no = 321;

 

   foo( pEmployee );

 

   return c;

}

 

Let’s compile this source to create an executable called emp:

gcc -o emp employee.c

 

Now let’s assume that our newly created emp executable is a massive complex commercial application. Let’s also assume that there is a functional problem somewhere in this massive program, and we’ve determined that a key to determining the cause is something that is being done in the function, foo. Also assume that we don’t have access to all of the source code, but we do know the structure of each employee record. If we run emp under GDB, we’re limited to working with addresses in memory, translating sizes, and converting hex to more meaningful values because the program was not compiled with debug symbols (the -g compile switch) and the source code is not readily available.

现在让我们假设我们新创建的 emp 可执行文件是一个庞大的复杂的商业应用程序。让我们也假设在这个庞大的程序中有一个功能性问题, 我们已经确定了确定原因的关键是在函数foo中。也假设我们不能访问所有的源代码, 但我们知道每个员工记录的结构。如果我们在 GDB 下运行 emp, 我们只限于处理内存中的地址、转换大小和将十六进制转换为更有意义的值, 因为程序不是用调试符号 (g 编译开关) 编译的, 而且源代码不容易获得。

Instead of struggling through assembly and raw memory, we can make use of the LD_PRELOAD environment variable, which Linux’s dynamic loader recognizes. When this environment variable is set to a valid library, the loader will load it before any other libraries get loaded. This gives us the opportunity to make our GDB session aware of any structure types we wish. To begin, create structure.c with the following code:

我们可以利用 Linux 的动态加载器识别的 LD_PRELOAD 环境变量, 而不是通过汇编和原始内存进行分析。当此环境变量设置为有效的库时, 加载程序将在加载任何其他库之前加载它。这使我们有机会让 GDB了解我们希望的结构类型。首先, 用以下代码创建structure.c:

#include "employee.h"

 

struct employee_rec *floating_rec;

 

Compile this source file with the following command (-shared is used to create a shared library, and employee.c contains the simple source code from the preceding script):

使用以下命令编译此源文件 (-共享用于创建共享库, 而employee. c 包含前面脚本中的简单源代码):

gcc -shared -o libstructure.so -g employee.c

 

This will create a shared library called libstructure.so containing debug symbols. Next, we set the LD_PRELOAD environment variable to point to this file:

这将创建一个名为 libstructure.so 包含调试符号的共享库。接下来, 我们将 LD_PRELOAD 环境变量设置为指向此文件:

export LD_PRELOAD=/home/dbehman/structure.so

 

Now when we re-run emp under a debugger, our employee_rec structure will be known, and we can point our floating_rec pointer to any point in memory where this structure begins and print it out. To demonstrate, first launch emp under GDB and set a breakpoint in the function foo:

现在, 当我们在调试器下重新运行 emp 时, 我们的 employee_rec 结构将被知道, 我们可以将我们的 floating_rec 指针指向内存中的任何点, 从这个结构开始并将其打印出来。为了演示, 首先在 GDB 下启动 emp, 并在函数 foo 中设置一个断点:

penguin> gdb emp

GNU gdb 5.3.92

Copyright 2003 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 "i586-suse-linux"...

(gdb) break foo

Breakpoint 1 at 0x804836f

(gdb)

 

Run emp, and when the breakpoint is hit, dump the assembly:

(gdb) run

Starting program: /home/dbehman/testing/emp

 

Breakpoint 1, 0x0804836f in foo ()

(gdb) disas

Dump of assembler code for function foo:

0x0804836c <foo+0>:     push   %ebp

0x0804836d <foo+1>:     mov    %esp,%ebp

0x0804836f <foo+3>:     mov    0x8(%ebp),%eax

0x08048372 <foo+6>:     movl   $0x1,0x4(%eax)

0x08048379 <foo+13>:    mov    $0x0,%eax

0x0804837e <foo+18>:    pop    %ebp

0x0804837f <foo+19>:    ret

End of assembler dump.

(gdb)

 

The first two instructions are standard, used to set up the stack for this function and comprise what is referred to as the function prologue (see Chapter 5 for more information). The third instruction copies an 8-byte value from the stack into the eax register. This is the address of the structure in memory that we want to look at. We can see by the message “Breakpoint 1, 0x0804836f in foo ()” from GDB above that the next instruction to be executed is the copying of our address into eax at 0x0804836f. Have the debugger execute this instruction and then examine the contents of eax.

前两个指令是标准的, 用于设置此函数的栈, 幷包含称为函数的序言 (参见5章以了解更多信息)。第三个指令将8字节的值从栈复制到 eax 寄存器中。这是我们要查看的内存中结构的地址。我们可以看到, 从上面的消息 "breakpoint 1, 0x0804836f in foo ()", 将执行的下一个指令是复制我们的地址到在0x0804836f的 eax。让调试器执行此指令, 然后检查 eax 的内容。

(gdb) stepi

0x08048372 in foo ()

(gdb) print /x $eax

$1 = 0x8049690

(gdb)

 

Now that we know the address of our structure, we can examine the raw memory contents. By examining the structure in employee.h, we know that the size of our structure is 56 bytes. When examining memory, a count is specified, and because we want to see words of size four bytes each, we use a count of 14 (56 divided by 4).

现在我们知道了结构的地址, 我们可以检查原始内存内容。通过检查employee. h中的结构, 我们知道我们的结构的大小是56字节。在检查内存时, 指定了一个计数, 因为我们希望看到每个大小为四字节的字, 我们使用的计数为 14 (56 除以 4)。

(gdb) x /14wx $eax

0x8049690:     0x00001140     0x00000000     0x006e6144     0x00000000

0x80496a0:     0x00000000     0x00000000     0x00000000     0x6d686542

0x80496b0:     0x00006e61     0x00000000     0x00000000     0x00000000

0x80496c0:     0x00000cce      0x00000141

(gdb)

 

There’s our structure in raw memory form! It is possible to get the information we’re looking for out of this representation, but a lot of conversion is required to get the correct values. Endian-ness must also be considered on x86-based platforms (which this is), so another layer of conversion is required. See the section, “Understanding and Dealing with Endian-ness,” that follows for more information. All of the conversion involved introduces many chances for human error and being off by one digit can put an analyst down the wrong path and potentially cause a loss of hours in debugging time.

在原始的内存中有我们的结构形式!可以从这些数据中获取我们要查找的信息, 但是需要进行大量的转换才能获得正确的值。在 x86 平台上也必须考虑字节序, 因此需要另一层转换。有关详细信息, 请参阅 "了解和处理字节序" 一节。所涉及的所有转换都引入了许多人为错误的机会, 并且被一个数字的丢失会使分析人员走上错误的路径, 并可能导致调试时间数小时的延长。

Fortunately, we’ve preloaded a library that contains debug symbols for the structure we want. All we have to do now is point our dummy structure pointer at this memory, and then we can print it out normally!

幸运的是, 我们已经预装了一个库, 其中包含了我们想要的结构的调试符号。我们现在要做的就是把我们的虚拟结构指针指向这个内存, 然后我们就可以正常打印出来了!

(gdb) set variable floating_rec = $eax

(gdb) print *floating_rec

$2 = {employee_no = 4416, is_ceo = 0,

  first_name = "Dan", '\0' <repeats 16 times>,

  last_name = "Behman", '\0' <repeats 13 times>,

  manager_emp_no = 3278, department_no = 321}

(gdb)

 

As you can see, this way it is much easier to see what the values are and what they refer to. Let’s now execute the next machine instruction and dump our structure again.

正如您所看到的, 这种方式更容易看到值, 以及它们所引用的内容。现在, 让我们执行下一台机器指令, 再转储我们的结构。

(gdb) stepi

0x08048379 in foo ()

(gdb) print *floating_rec

$3 = {employee_no = 4416, is_ceo = 1,

  first_name = "Dan", '\0' <repeats 16 times>,

  last_name = "Behman", '\0' <repeats 13 times>,

  manager_emp_no = 3278, department_no = 321}

(gdb)

 

We can easily see that the is_ceo field was changed from 0 to 1. We now know that the function foo() performs a very significant promotion of a regular employee to a Chief Executive Officer!

This example is scaled down, but it should show how valuable this technique can be, especially when working with large and complex data structures.

我们可以很容易地看到, is_ceo 字段从0改为1。我们现在知道的函数 foo () 执行一个从普通员工到首席执行官的非常重要的晋升! 此示例已缩小, 但它应该显示此技术的价值, 尤其是在处理大型复杂数据结构时。

6.9.4. Understanding and Dealing with Endian-ness

The term endian refers to how a particular architecture stores data in memory. Specifically, the order in which the bytes are stored is dictated by the endian-ness of a particular architecture.

术语 "endian" 是指特定体系结构如何将数据存储在内存中。具体地说, 存储字节的顺序由特定体系结构的字节序决定。

When viewing raw memory in a debugger or a hex dump of some kind, it is very important to know whether the system is big or little endian. If the system is big endian, no conversion is needed, but if the system is little endian, you will have to convert the raw data into the human-preferred form (big endian) when reading it.

当在调试器或某种十六进制转储中查看原始内存时, 了解系统是大还是小的字节序是非常重要的。如果系统是大的字节序, 不需要转换, 但如果系统是小的字节序, 您将必须转换原始的数据转换为可读的形式 (大字节序)。

Consider the following simple program:

int main()

{

  unsigned long long i64 = 0x0123456789abcdef ;

  char  *tmpPtr ;

 

  return 0 ;

}

 

This simple program sets the value of a 64-bit variable i64 to 0x0123456789abcdef. The program also includes a character pointer, tmpPtr, that will be used from within GDB to determine the order of the bytes for the 64-bit value as it is stored in memory. Let’s see the byte order for this 64-bit value from within GDB:

此简单程序将64位变量 i64 的值设置为0x0123456789abcdef。该程序还包括一个字符指针, tmpPtr, 将在 GDB 内确定64位值的字节顺序, 当它存储在内存中。让我们从 GDB 内部查看这个64位值的字节顺序:

Code View: Scroll / Show All

penguin> g++ endian.c -o endian -g

penguin> gdb endian

GNU gdb 5.2.1

Copyright 2002 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 "i586-suse-linux"...

(gdb) break main

Breakpoint 1 at 0x804836c: file endian.c, line 4.

(gdb) run

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

 

Breakpoint 1, main () at endian.c:4

4     unsigned long long i64 = 0x0123456789abcdef ;

Current language: auto; currently c++

(gdb) next

7     return 0 ;

(gdb) set tmpPtr = &i64

(gdb) printf "%x\n", (unsigned char)tmpPtr[0]

ef

(gdb) printf "%x\n", (unsigned char)tmpPtr[1]

cd

(gdb) printf "%x\n", (unsigned char)tmpPtr[2]

ab

(gdb) printf "%x\n", (unsigned char)tmpPtr[3]

89

(gdb) printf "%x\n", (unsigned char)tmpPtr[4]

67

(gdb) printf "%x\n", (unsigned char)tmpPtr[5]

45

(gdb) printf "%x\n", (unsigned char)tmpPtr[6]

23

(gdb) printf "%x\n", (unsigned char)tmpPtr[7]

   1

 

In the GDB session, we set the character pointer to the address of the 64-bit variable with set tmpPtr = &i64. Because the pointer is a character pointer, we can use it as a character array to find each byte of the 64-bit value, i64. Each index into the character array (for example, tmpPtr[3]) shows another byte, in order, of the 64-bit value as it is stored in memory. From the GDB output here, the 64-bit value of 0x0123456789abcdef is actually stored as 0xefcdab8967452301 in memory. In other words, the byte order is completely reversed.

在 GDB 中, 我们用 set tmpPtr = &i64 将字符指针设置为64位变量的地址。因为指针是一个字符指针, 所以我们可以使用它作为字符数组来查找64位值 i64 的每个字节。字符数组中的每个索引 (例如, tmpPtr [3]) 显示了在内存中存储的64位值的每一个字节 (按顺序)。从 GDB 的输出中, 0x0123456789abcdef 的64位值实际上存储为内存中的0xefcdab8967452301。换言之, 字节顺序完全颠倒。

Note: Byte reversing only need be done when viewing data that is in words two or more bytes in size. Individual bytes are always displayed as expected.

注意: 仅当查看字大小中的两个或更多字节的数据时, 才需要执行字节反转。每个字节总是按预期显示。

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