| 风声竹影 的个人资料听风竹轩的书架日志列表网络 | 帮助 |
|
12月31日 Hello World 背后的真实故事Hello World 背后的真实故事(至少是大部分故事) * 原作者:Antônio Augusto M. Fröhlich * 译者:杨文博 <http://blog.solrex.cn> 我们计算机科学专业的大多数学生至少都接触过一回著名的 "Hello World" 程序。相比一个典型的应用程序——几乎总是有一个带网络连接的图形用户界面,"Hello World" 程序看起来只是一段很简单无趣的代码。不过,许多计算机科学专业的学生其实并不了解它背后的真实故事。这个练习的目的就是利用对 "Hello World" 的生存周期的分析来帮助你揭开它神秘的面纱。 源代码 让我们先看一下 Hello World 的源代码: 1. #include <stdio.h> 第 1 行指示编译器去包含调用 C 语言库(libc)函数 printf 所需要的头文件声明。 第 3 行声明了 main 函数,看起来好像是我们程序的入口点(在后面我们将看到,其实它不是)。它被声明为一个不带参数(我们这里不准备理会命令行参数)且会返回一个整型值给它 的父进程(在我们的例子里是 shell)的函数。顺便说一下,shell 在调用程序时对其返回值有个约定:子进程在结束时必须返回一个 8 比特数来代表它的状态:0 代表正常结束,0~128 中间的数代表进程检测到的异常终止,大于 128 的数值代表由信号引起的终止。 从第 4 行到第 8 行构成了 main 函数的实现,即调用 C 语言库函数 printf 输出 "Hello World!\n" 字符串,在结束时返回 0 给它的父进程。 简单,非常简单! 编译 现在让我们看看 "Hello World" 的编译过程。在下面的讨论中,我们将使用非常流行的 GNU 编译器(gcc)和它的二进制辅助工具(binutils)。我们可以使用下面命令来编译我们的程序: # gcc -Os -c hello.c 这样就生成了目标文件 hello.o,来看一下它的属性: # file hello.o 给出的信息告诉我们 hello.o 是个可重定位的目标文件(relocatable),为 IA-32(Intel Architecture 32) 平台编译(在这个练习中我使用了一台标准 PC),保存为 ELF(Executable and Linking Format) 文件格式,并且包含着符号表(not stripped)。 顺便: # objdump -hrt hello.o Sections: SYMBOL TABLE: RELOCATION RECORDS FOR [.text]: 这告诉我们 hello.o 有 5 个段: (译者注:在下面的解释中读者要分清什么是 ELF 文件中的段(section)和进程中的段(segment)。比如 .text 是 ELF 文件中的段名,当程序被加载到内存中之后,.text 段构成了程序的可执行代码段。其实有时候在中文环境下也称 .text 段为代码段,要根据上下文分清它代表的意思。) 1. .text: 这是 "Hello World" 编译生成的可执行代码,也就是说这个程序对应的 IA-32 指令序列。.text 段将被加载程序用来初始化进程的代码段。 2. .data:"Hello World" 的程序里既没有初始化的全局变量也没有初始化的静态局部变量,所以这个段是空的。否则,这个段应该包含变量的初始值,运行前被装载到进程的数据段。 3. .bss: "Hello World" 也没有任何未初始化的全局或者局部变量,所以这个段也是空的。否则,这个段指示的是,在进程的数据段中除了上文的 .data 段内容,还有多少字节应该被分配并赋 0。 4. .rodata: 这个段包含着被标记为只读 "Hello World!\n" 字符串。很多操作系统并不支持进程(运行的程序)有只读数据段,所以 .rodata 段的内容既可以被装载到进程的代码段(因为它是只读的),也可以被装载到进程的数据段(因为它是数据)。因为编译器并不知道你的操作系统所使用的策略,所 以它额外生成了一个 ELF 文件段。 5. .comment:这个段包含着 33 字节的注释。因为我们在代码中没有写任何注释,所以我们无法追溯它的来源。不过我们将很快在下面看到它是怎么来的。 它也给我们展示了一个符号表(symbol table),其中符号 main 的地址被设置为 00000000,符号 puts 未定义。此外,重定位表(relocation table)告诉我们怎么样去在 .text 段中去重定位对其它段内容的引用。第一个可重定位的符号对应于 .rodata 中的 "Hello World!\n" 字符串,第二个可重定位符号 puts,代表了使用 printf 所产生的对一个 libc 库函数的调用。为了更好的理解 hello.o 的内容,让我们来看看它的汇编代码: 1. # gcc -Os -S hello.c -o - 从汇编代码中我们可以清楚的看到 ELF 段标记是怎么来的。比如,.text 段是 32 位对齐的(第 7 行)。它也揭示了 .comment 段是从哪儿来的(第 20 行)。因为我们使用 printf 来打印一个字符串,并且我们要求我们优秀的编译器对生成的代码进行优化(-Os),编译器用(应该更快的) puts 调用来取代 printf 调用。不幸的是,我们后面将会看到我们的 libc 库的实现会使这种优化变得没什么用。 那么这段汇编代码会生成什么代码呢?没什么意外之处:使用标志字符串地址的标号 .LCO 作为参数的一个对 puts 库函数的简单调用。 连接 下面让我们看一下 hello.o 转化为可执行文件的过程。可能会有人觉得用下面的命令就可以了: # ld -o hello hello.o -lc 不过,那个警告是什么意思?尝试运行一下! 是的,hello 程序不工作。让我们回到那个警告:它告诉我们连接器(ld)不能找到我们程序的入口点 _start。不过 main 难道不是入口点吗?简短的来说,从程序员的角度来看 main 可能是一个 C 程序的入口点。但实际上,在调用 main 之前,一个进程已经执行了一大堆代码来“为可执行程序清理房间”。我们通常情况下从编译器或者操作系统提供者那里得到这些外壳程序 (surrounding code,译者注:比如 CRT)。 下面让我们试试这个命令: # ld -static -o hello -L`gcc -print-file-name=` /usr/lib/crt1.o /usr/lib/crti.o hello.o /usr/lib/crtn.o -lc -lgcc 现在我们可以得到一个真正的可执行文件了。使用静态连接(static linking)有两个原因:一,在这里我不想深入去讨论动态连接库(dynamic libraries)是怎么工作的;二,我想让你看看在我们库(libc 和 libgcc)的实现中,有多少不必要的代码将被添加到 "Hello World" 程序中。试一下这个命令: # find hello.c hello.o hello -printf "%f\t%s\n" 你也可以尝试 "nm hello" 和 "objdump -d hello" 命令来得到什么东西被连接到了可执行文件中。 想了解动态连接的更多内容,请参考 Program Library HOWTO 装载和运行 在一个遵循 POSIX(Portable Operating System Interface) 标准的操作系统(OS)上,装载一个程序是由父进程发起 fork 系统调用来复制自己,然后刚生成的子进程发起 execve 系统调用来装载和执行要运行的程序组成的。无论何时你在 shell 中敲入一个外部命令,这个过程都会被实施。你可以使用 truss 或者 trace 命令来验证一下: # strace -i hello > /dev/null 除了 execve 系统调用,上面的输出展示了打印函数 puts 中的 write 系统调用,和用 main 的返回值(0)作为参数的 exit 系统调用。 为了解 execve 实施的装载过程背后的细节,让我们看一下我们的 ELF 可执行文件: # readelf -l hello Program Headers: Section to Segment mapping: 输出显示了 hello 的整体结构。第一个程序头对应于进程的代码段,它将从文件偏移 0x000000 处被装载到映射到进程地址空间的 0x08048000 地址的物理内存中(虚拟内存机制)。代码段共有 0x55dac 字节大小而且必须按页对齐(0x1000, page-aligned)。这个段将包含我们前面讨论过的 ELF 文件中的 .text 段和 .rodata 段的内容,再加上在连接过程中生成的附加的段。正如我们预期,它被标志为:只读(R)和可执行(X),不过禁止写(W)。 第二个程序头对应于进程的数据段。装载这个段到内存的方式和上面所提到的一样。不过,需要注意的是,这个段占用的文件大小是 0x01df4 字节,而在内存中它占用了 0x03240 字节。这个差异主要归功于 .bss 段,它在内存中只需要被赋 0,所以不用在文件中出现(译者注:文件中只需要知道它的起始地址和大小即可)。进程的数据段仍然需要按页对齐(0x1000, page-aligned)并且将包含 .data 和 .bss 段。它将被标识为可读写(RW)。第三个程序头是连接阶段产生的,和这里的讨论没有什么关系。 如果你有一个 proc 文件系统,当你得到 "Hello World" 时停止进程(提示: gdb,译者注:用 gdb 设置断点),你可以用下面的命令检查一下是不是如上所说: # cat /proc/`ps -C hello -o pid=`/maps 第一个映射的区域是这个进程的代码段,第二个和第三个构成了数据段(data + bss + heap),第四个区域在 ELF 文件中没有对应的内容,是程序栈。更多和正在运行的 hello 进程有关的信息可以用 GNU 程序:time, ps 和 /proc/pid/stat 得到。 程序终止 当 "Hello World" 程序运行到 main 函数中的 return 语句时,它向我们在段连接部分讨论过的外壳函数传入了一个参数。这些函数中的某一个发起 exit 系统调用。这个 exit 系统调用将返回值转交给被 wait 系统调用阻塞的父进程。此外,它还要对终止的进程进行清理,将其占用的资源还给操作系统。用下面命令我们可以追踪到部分过程: # strace -e trace=process -f sh -c "hello; echo $?" > /dev/null 结束 这个练习的目的是让计算机专业的新生注意这样一个事实:一个 Java Applet 的运行并不是像魔法一样(无中生有的),即使在最简单的程序背后也有很多系统软件的支撑。如果您觉得这篇文章有用并且想提供建议来改进它,请发电子邮件给我。 常见问题 这一节是为了回答学生们的常见问题。 * 什么是 "libgcc"? 为什么它在连接的时候被包含进来? The True Story of Hello World http://www.lisha.ufsc.br/~guto/ The True Story of Hello World(or at least a good part of it)Most of our computer science students have been through the famous "Hello World" program at least once. When compared to a typical application program ---almost always featuring a web-aware graphical user interface, "Hello World" turns into an very uninteresting fragment of code. Nevertheless, many computer science students still didn't get the real story behind it. The goal of this exercise is to cast some light in the subject by snooping in the "Hello World" life-cycle. The source codeLet's begin with Hello World's source code:
Line 1 instructs the compiler to include the declarations needed to invoke the printf C library (libc) function. Line 3 declares function main, which is believed to be our program entry point (it is not, as we will see later). It is declared as a function that takes no parameter (we disregard command line arguments in this program) and returns an integer to the parent process --- the shell, in our case. By the way, the shell dictates a convention by which a child process must return an 8-bit number representing it status: 0 for normal termination, 0 > n < 128 for process detected abnormal termination, and n > 128 for signal induced termination. Line 4 through 8 comprise the definition of function main, which invokes the printf C library function to output the "Hello World!\n" string and returns 0 to the parent process. Simple, very simple! CompilationNow let's take a look at the compilation process for "Hello World". For the upcoming discussion, we'll take the widely-used GNU compiler (gcc) and its associated tools (binutils). We can compile the program as follows: # gcc -Os -c hello.c This produces the object file hello.o. More
specifically,
# file hello.o tells us hello.o is a relocatable object file, compiled for the IA-32 architecture (I used a standard PC for this study), stored in the Executable and Linking Format (ELF), that contains a symbol table (not stripped). By the way,
# objdump -hrt hello.o tells us hello.o has 5 sections:
It also shows us a symbol table with symbol main bound to address 00000000 and symbol puts undefined. Moreover, the relocation table tells us how to relocate the references to external sections made in section .text. The first relocatable symbol corresponds to the "Hello World!\n" string contained in section .rodata. The second relocatable symbol, puts, designates a libc function which was generated as a result of invoking printf. To better understand the contents of hello.o, let's take a look at the assembly code:
From the assembly code, it becomes clear where the ELF section flags come from. For instance, section .text is to be 32-bit aligned (line 7). It also reveals where the .comment section comes from (line 20). Since printf was called to print a single string, and we requested our nice compiler to optimize the generated code (-Os), puts was generated instead. Unfortunately, we'll see later that our libc implementation will render the compiler effort useless. And what about the assembly code produced? No surprises here: a simple call to function puts with the string addressed by .LC0 as argument. LinkingNow let's take a look at the process of transforming
hello.o into an executable. One might think the following
command would do:
# ld -o hello hello.o -lc But what's that warning? Try running it! Yes, it doesn't work. So let's go back to that warning: it tells the linker couldn't find our program's entry point _start. But wasn't it main our entry point? To be short here, main is the start point of a C program from the programmer's perspective. In fact, before calling main, a process has already executed a bulk of code to "clean up the room for execution". We usually get this surrounding code transparently from the compiler/OS provider. So let's try this: # ld -static -o hello -L`gcc -print-file-name=` /usr/lib/crt1.o /usr/lib/crti.o hello.o /usr/lib/crtn.o -lc -lgcc Now we should have a real executable. Static linking was used for
two reasons: first, I don't want to go into the discussion of how
dynamic libraries work here; second, I'd like to show you how much
unnecessary code comes into "Hello World" due to the way libraries
(libc and libgcc) are implemented. Try the following:
# find hello.c hello.o hello -printf "%f\t%s\n" You can also try "nm hello" or "objdump -d hello" to get an idea of what got linked into the executable. For information about dynamic linking, please refer to Program Library HOWTO. Loading and runningIn a POSIX OS, loading a program for execution is accomplished by
having the father process to invoke the fork system
call to replicates itself and having the just-created child
process to invoke the execve system call to load and start
the desired program. This procedure is carried out, for instance, by
the shell whenever you type an external command. You can
confirm this with truss or strace:
# strace -i hello > /dev/null Besides the execve system call, the output shows the call to write that results from puts, and the call to exit with the argument returned by function main (0). To understand the details behind the loading procedure carried
out by execve, let's take a look at our ELF executable:
# readelf -l hello The output shows the overall structure of hello. The first program header corresponds to the process' code segment, which will be loaded from file at offset 0x000000 into a memory region that will be mapped into the process' address space at address 0x08048000. The code segment will be 0x55dac bytes large and must be page-aligned (0x1000). This segment will comprise the .text and .rodata ELF segments discussed earlier, plus additional segments generated during the linking procedure. As expected, it's flagged read-only (R) and executable (X), but not writable (W). The second program header corresponds to the process' data segment. Loading this segment follows the same steps mentioned above. However, note that the segment size is 0x01df4 on file and 0x03240 in memory. This is due to the .bss section, which is to be zeroed and therefore doesn't need to be present in the file. The data segment will also be page-aligned (0x1000) and will contain the .data and .bss ELF segments. It will be flagged readable and writable (RW). The third program header results from the linking procedure and is irrelevant for this discussion. If you have a proc file system, you can check this, as
long as you get "Hello World" to run long enough (hint: gdb),
with the following command:
# cat /proc/`ps -C hello -o pid=`/maps The first mapped region is the process' code segment, the second and third build up the data segment (data + bss + heap), and the fourth, which has no correspondent in the ELF file, is the stack. Additional information about the running hello process can be obtained with GNU time, ps, and /proc/pid/stat. TerminatingWhen "Hello World" executes the return statement in
main function, it passes a parameter to the surrounding
functions discussed in section linking. One of these functions
invokes the exit system call passing by the return
argument. The exit system call hands over that value to the
parent process, which is currently blocked on the wait
system call. Moreover, it conducts a clean process termination, with
resources being returned to the system. This procedure can be
partially traced with the following:
# strace -e trace=process -f sh -c "hello; echo $?" > /dev/null ClosingThe intention of this exercise is to call attention of new computer science students to the fact that a Java applet doesn't get run by magic: there's a lot of system software behind even the simplest program. If consider it useful and have any suggestion to improve it, please e-mail me. FAQThis section is dedicated to student's frequently asked questions.
ARM GNU合集 http://www.fivemanconspiracy.com/forum/4 这里有些openocd gdb gcc开发arm的文章 http://www.pdp8.net/other/freertos_linux/install.shtml FreeRTOS Stellaris Setup under Linux http://www.codesourcery.com 致力于开发gnu toolchains产品的公司 http://en.wikipedia.org/wiki/ARM_architecture 12月30日 用Diff和Patch工具维护源码
diff和patch使用指南diff和patch是一对工具,在数学上来说,diff是对两个集合的差运算,patch是对两个集合的和运算。 这就是diff和patch的妙处。下面分别介绍一下两个工具的用法: diff后面可以接两个文件名或两个目录名。 如果是一个目录名加一个文件名,那么只作用在那么个目录下的同名文件。 如果是两个目录的话,作用于该目录下的所有文件,不递归。如果我们希望递归执行,需要使用-r参数。 命令diff A B > C ,一般A是原始文件,B是修改后的文件,C称为A的补丁文件。 patch用于根据原文件和补丁文件生成目标文件。还是拿上个例子来说 patch A C 就能得到B, 这一步叫做对A打上了B的名字为C的补丁。 之一步之后,你的文件A就变成了文件B。如果你打完补丁之后想恢复到A怎么办呢? patch -R B C 就可以重新还原到A了。 所以不用担心会失去A的问题。 其实patch在具体使用的时候是不用指定原文件的,因为补丁文件中都已经记载了原文件的路径和名称。patch足 够聪明可以认出来。但是有时候会有点小问题。比如一般对两个目录diff的时候可能已经包含了原目录的名字,但是我们打补丁的时候会进入到目录中再使用 patch,着个时候就需要你告诉 patch命令怎么处理补丁文件中的路径。可以利用-pn开关,告诉patch命令忽略的路径分隔符的个数。举例如下: A文件在 DIR_A下,修改后的B文件在DIR_B下,一般DIR_A和DIR_B在同一级目录。我们为了对整个目录下的所有文件一次性diff,我们一般会到DIR_A和DIR_B的父目录下执行以下命令 diff -rc DIR_A DIR_B > C 这个时候补丁文件C中会记录了原始文件的路径为 DIR_A/A 现在另一个用户得到了A文件和C文件,其中A文件所在的目录也是DIR_A。 一般,他会比较喜欢在DIR_A目录下面进行patch操作,它会执行 patch < C 但是这个时候patch分析C文件中的记录,认为原始文件是./DIR_A/A,但实际上是./A,此时patch会找不到原始文件。为了避免这种情况我们可以使用-p1参数如下 patch -p1 < C 此时,patch会忽略掉第1个”/”之前的内容,认为原始文件是 ./A,这样就正确了。 最后有以下几点注意: 1. 一次打多个patch的话,一般这些patch有先后顺序,得按次序打才行。 12月21日 GNU ld链接脚本学习[转]
链接器脚本 *********** 每次一次链接行为都是被链接器脚本控制,这样的脚本是采用链接器命令语言写成. 这种脚本主要用途描述如何把输入文件中各段(section)组织到输出文件,并控制输出输出文件的内存布局.大部分链接脚本也就做这些操作.有特殊需要时,链接脚本也可以直接指示链接器使用下面列出的命令执行其它行为. 链接器总是使用链接脚本.如果没有提供自定义链接脚本则链接器使用默认脚本编译进可执行文件. 查看默认链接器脚本可以使用命令行选项'--verbose'即:ld --verbose;有些命令行选项例如'-r'、'-N'的使用会影响到默认脚本. 如果想使用自己的链接脚本可以使用'-T'命令行选项,指定这个选项后会替换默认脚本. 你也可以以隐含方式使用链接脚本就像命名的输入文件,好像也被链接一样.看Implicit Linker Scripts部分. *Menu: ** Basic Script Concepts:: 链接脚本基本概念 ** Script Format:: 链接脚本格式 ** Simple Example:: 简单样例 ** Simple Commands:: 简单命令 ** Assignments:: 分配值给符号(symbols) ** SECTIONS:: 段命令 **MEMORY:: 内存命令 **PHDRS:: PHDRS 命令 **VERSION:: 版本命令 **Expressions:: 脚本使用的表达式 **Implicit Linker Scripts:: 隐示包含链接脚本 Basic Linker Script Concepts ============================ 为更好描述链接器脚本语言,我们要定义些基本概念和名词. 链接器把所有输入文件组织到单个输出文件.输出文件和输入文件都是以特定数据格式存放,就是众所周知的'目标文件'格式.每个文件都被称作目标文件,输出文件通常称作可执行文件,但是为表达方便也称其为目标文件.每个目标文件(排除特殊情况)都有一个段列表.有时我们把输入文件中的段称作输入段(input section),类似地,也会将输出文件段叫它输出段. 目标文件中每个段都有一个名字和大小,大部分段也有一个与其关联的数据块,称其为'段注释'.段可是标记为'可装入的(loadable)',意思是输出文件运行时上下文是要被装入内存;没有注释的段标记为'可分配的',意思是在内存的这块区域应该被分配,但是没有任何其它内容装入这里(某些情况下必须被置0).既不是可装入又不是可分配的典型段就是调试信息类的. 每个可装入的或者可分配的输出段会有两个地址:VMA--虚拟内存地址,LMA--装入内存地址.VMA是输出文件运行是段的地址,LMA是段被装入的位置地址,多数情况下两个地址相同.也有不同的情况,例如一个数据地段被装入ROM,当程序启动时将这个数据段拷贝到RAM(这种技术通常用于初始化ROM系统的全局变量.此时ROM地址是LMA,RAM地址是VMA. 你可以使用带有'-h'命令行选项的objdump显示目标文件的段. 每个目标文件也都有一个符号列表,称作符号表(symbol table),符号可以是已定义或未定义的.每个符号具有名字、地址等其它信息.当编译C或C++源程序成目标文件,对于每个定义的函数、全局和静态变量都会产生一个符号;每个未定义函数、全局变量作为输入文件的地址将转变成未定义符号. 显示目标文件中符号可以使用下面两个命令: #nm obj #objdump -t obj Linker Script Format ==================== 链接脚本是文本文件. 链接脚本由命令串构成,每个命令要么是关键字,可能带有参数;要么是指定的符号.使用分号(';')分割命令,空格在处理时被忽略. 类似文件、格式名字这样的字符串通常可以直接输入,如果文件名包含其它字符像点('.')号等其它字符分割文件名字,可是使用双引号("")包含.没有情况会在文件名中使用双引号. 链接脚本像C一样可以包含注释,也使用'/*'和'*/',在C里注释作为空格处理. Simple Linker Script Example ============================ 不少链接脚本是比较简单明了. 最简单的链接脚本只有一个命令'SECTIONS',使用'SECTIONS'命令描述输出文件内存布局. 'SECTIONS'命令是非常强大的,在这我们将解释它的简单用法.现在假设你的程序只是由单一代码段、初始化数段和未初始化数据段,分别对应'.text'、'.data'、'.bss'的段名.再假设输入文件中也只出现这些段. 在这个例子中,制定代码段装入地址在0x10000,数据段开始在0x8000000,下面脚本即是脚本描述: SECTIONS { . = 0x10000; .text : { *(.text) } . = 0x8000000; .data : { *(.data) } .bss : { *(.bss) } } 关键字'SECTIONS'作为命令,后面跟随的是花括弧中包含符号分配和输出描述串. 在'SECTIONS'里面第一行特殊符号'.'是用作位置计数.如果你没有以其它方式指定输出段地址(其它方式以后在讲解),则地址被设置成位置计数的当前值.位置计数依从输出区大小增长,在'SECTIONS'命令起始处位置计数赋值为0. 第二行定义输出区'.text',冒号是语法必须的,现在可以忽略.输出区名字后面的花括弧内,列出所有输入区名字,他们将被安置到输出区中.'*'是通配符,匹配任何文件名.表达式'*(.text))意思是所有输入文件的输入区. 因为位置计数在输出区中定义为0x10000,链接器就会设置输出文件'.text'地址到0x10000. 剩下行定义输出文件'.data'和'.bss'区,链接器将安排'.data'输出区在地址0x8000000.链接器安排好'.data'输出区后,位置计数的值将是0x8000000加'.data'输出大小,作用就是链接器在内存中紧跟'.data'输出区安排'.bss'输出区. 链接器确保每个输出区是对齐的,需要时通过增长位置计数.这个例子中指定'.text'和'.data'区地址将满足强制对齐,但是链接器可能会建立间隙在'.data'和'.bss'之间. 到此为止,这就是一个简单而又完整的链接脚本. Simple Linker Script Commands ============================= 这部分中讲述简单的链接器脚本命令. * Menu: * Entry Point:: 设置入口点 * File Commands:: 文件处理命令 * Format Commands:: 目标文件格式命令 * Miscellaneous Commands:: 其它链接命令 Setting the entry point ----------------------- 程序执行的第一条指令称作'入口点'(entry point).你可以使用链接器脚本命令设置入口点,它的参数是符号名字: ENTRY(SYMBOL) 有几种方式设置入口点,链接器按照顺序设置每个入口点,知道有一个成功: *'-e' ENTRY命令行选项 *'ENTRY(SYMBOL)命令使用在脚本中 *符号'start'地址,当然要预先定义'start' *'.text'区第一个字节地址 *地址0 Commands dealing with files --------------------------- 几个处理文件的脚本命令. 'INCLUDE FILENAME':在当前位置包含进脚本文件FILENAME,在当前目录和'-L'指定目录搜索FILENAME,嵌套调用可达10层深度. `INPUT(FILE, FILE, ...)' `INPUT(FILE FILE ...)':输入命令促使链接器在链接时包含命名的文件,就像在命令行指定名字. 例如,在链接时总是要包含'subr.o',但是有不愿每次链接时设置它,那么就可以在脚本中写'INPUT(subr.o)'. 其实,如果个人喜欢,你可以在脚本中列出所有输入文件,每次调用链接器不带任何东西除了'-T'选项. 链接器首先尝试打开当前目录下文件,如果没有找到,则链接器搜索库文件路径.查看'-L'描述在*Note Command Line Options: Options. 如果使用'INPUT(lFILE)',ld将转换名字为'libFILE.a',类似命令行参数'-l'. 当在隐含脚本文件使用'INPUT'命令,文件需要包含链接脚本也要包含,这可能影响档案文件所搜. `GROUP(FILE, FILE, ...)' `GROUP(FILE FILE ...)':这个命令类似'INPUT',但是所有命名文件完全是归档文件,它们被重复搜索直到不再有未定义的参考(references).请看'-('的描述在*Note Command Line Options: Options. `OUTPUT(FILENAME)':命名输出文件,使用`OUTPUT(FILENAME)'在脚本文件中和在命令行使用'-o FILENAME'完全相同.如果两者都使用,则命令行优先. 你也可以使用'output'命令定义默认输出文件名,不同于通常使用的'a.out'. `SEARCH_DIR(PATH)':添加路径到ld所搜归档库的路径列表中.使用这个命令效果和命令行'-L PATH'完全相同,如果两者都使用链接器将都搜索,命令行指定的路径优先搜索. `STARTUP(FILENAME)':此命令类似'INPUT',除却文件变成第一个被链接的,就像在命令行首先指定,这在使用固定系统很有用,因为入口点永远在第一个文件的开始. vim GDB本节所用命令的帮助入口: 在UNIX系统最初设计时,有一个非常重要的思想:每个程序只实现单一的功能,通过管道等方式把多个程序连接起来,使之协同工作,以完成更强大的功能。程序只实现单一功能,一方面降低了程序的复杂性,另一方面,也让它专注于这一功能,把这个功能做到最好。就好像搭积木一样,每个积木只提供简单的功能,但不同的积木垒在一起,就能搭出大厦、汽车等等复杂的东西。 从UNIX系统(及其变种)的命令行就可以看出这一点,每个命令只专注于单一的功能,但通过管道、脚本等把这些命令揉合起来,就能完成复杂的任务。 VI/VIM的设计也遵从这一思想,它只提供了文本编辑功能(与Emacs的大而全刚好相反),而且正如大家所看到的,它在这一领域做的是如此的出色。 也正因为如此,VIM自身并不提供集成开发环境所需的全部功能(它也不准备这样做,VIM只想成为一个通用的文本编辑器)。它把诸如编译、调试这样功能,交给更专业的工具去实现,而VIM只提供与这些工具的接口。 我们在前面已经介绍过VIM与编译器的接口(见quickfix主题),VIM也提供了与调试器的接口,这一接口就是netbeans。除此之外,还可以给VIM打一个补丁,以使其支持GDB调试器。我们在本篇以及下一篇分别介绍这两种方式。 由于netbeans接口只能在gvim中使用,而打上vimgdb补丁后,无论在终端的vim,还是gvim,都可以调试。所以我更喜欢打补丁的方式,本篇先介绍这种方法。 打补丁的方式,需要重新编译VIM,刚好借这个机会,介绍一下VIM的编译方法。我只介绍Linux上编译方法,如果你想在windows上编译vim,可以参考这篇文档:Vim: Compiling HowTo: For Windows。 [ 下载VIM源代码 ] 首先我们需要下载VIM的源码。到http://www.vim.org/sources.php下载当前最新的VIM 7.1的源代码,假设我们把代码放到~/install/目录,文件名为vim-7.1.tar.bz2。 [ 下载vimgdb补丁 ] 接下来,我们需要下载vimgdb补丁,下载页面在: http://sourceforge.net/project/showfiles.php?group_id=111038&package_id=120238 在这里,选择vim 7.1的补丁,把它保存到~/install/vimgdb71-1.12.tar.gz。 [ 打补丁 ] 运行下面的命令,解压源码文件,并打上补丁: [ 定制VIM的功能 ] 缺省的VIM配置已经适合大多数人,但有些时候你可能需要一些额外的功能,这时就需要自己定制一下VIM。定制VIM很简单,进入~/install/vim71/src文件,编辑Makefile文件。这是一个注释很好的文档,根据注释来选择: - 如果你不想编译gvim,可以打开--disable-gui选项; - 如果你想把perl, python, tcl, ruby等接口编译进来的话,打开相应的选项,例如,我打开了--enable-tclinterp选项; - 如果你想在VIM中使用cscope的话,打开--enable-cscope选项; - 我们刚才打的vimgdb补丁,自动在Makefile中加入了--enable-gdb选项; - 如果你希望在vim使用中文,使能--enable-multibyte和--enable-xim选项; - 可以通过--with-features=XXX选项来选择所编译的VIM特性集,缺省是--with-features=normal; - 如果你没有root权限,可以把VIM装在自己的home目录,这时需要打开prefix = $(HOME)选项; 编辑好此文件后,就可以编辑安装vim了。如果你需要更细致的定制VIM,可以修改config.h文件,打开/关闭你想要的特性。 [ 编译安装 ] 编译和安装VIM非常简单,使用下面两个命令: 你不需要手动运行./configure命令,make命令会自动调用configure命令。 上面的命令执行完后,VIM就安装成功了。 我在编译时打开了prefix = $(HOME)选项,因此我的VIM被安装在~/bin目录。这时需要修改一下PATH变量,以使其找到我编辑好的VIM。在~/.bashrc文件中加入下面这两句话: 退出再重新登录,现在再敲入vim命令,发现已经运行我们编译的VIM了。 [ 安装vimgdb的runtime文件 ] 运行下面的命令,解压vimgdb的runtime文件到你的~/.vim/目录: 现在启动VIM,在VIM中运行下面的命令以生成帮助文件索引: 现在,你可以使用“:help vimgdb”命令查看vimgdb的帮助了。 至此,我们重新编译了VIM,并为之打上了vimgdb补丁。下面我以一个例子来说明如何在VIM中完成“编码—编译—调试”一条龙服务。 [ 在VIM中调试 ] 首先确保你的计算机上安装了GDB ,Vimgdb支持5.3以上的GDB版本,不过最好使用GDB 6.0以上的版本。 我使用下面这个简单的例子,来示例一下如何在VIM中使用GDB调试。先来看示例代码: 文件~/tmp/sample.c内容如下,这是主程序,调用函数计算某数的阶乘并打印: 文件~/tmp/factor/factor.c内容如下,定义了子函数factor()。之所以把它放到子目录factor/,是为了演示vim可以自动根据调试位置打开文件,不管该文件在哪个目录下: Makefile文件,用来编译示例代码,最终生成的可执行文件名为sample。 假设vim的当前工作目录是~/tmp(使用“:cd ~/tmp”命令切换到此目录)。我们编辑完上面几个文件后,输入命令“:make”,vim就会根据Makefile文件进行编译。如果编译出错,VIM会跳到第一个出错的位置,改完后,用“:cnext”命令跳到下一个错误,以此类推。这种开发方式被称为quickfix,我们已经在前面的文章中讲过,不再赘述。 现在,假设已经完成链接,生成了最终的sample文件,我们就可以进行调试了。 Vimgdb补丁已经定义了一些键绑定,我们先加载这些绑定: 加载后,一些按键就被绑定为调试命令(Vimgdb定义的键绑定见“:help gdb-mappings”)。按<F7>键可以在按键的缺省定义和调试命令间切换。 好了,我们现在按空格键,在当前窗口下方会打开一个小窗口(command-line窗口),这就是vimgdb的命令窗口,可以在这个窗口中输入任何合法的gdb命令,输入的命令将被送到gdb执行。现在,我们在这个窗口中输入“gdb”,按回车后,command-line窗口自动关闭,而在当前窗口上方又打开一个窗口,这个窗口是gdb输出窗口。现在VIM的窗口布局如下(我又按空格打开了command-line窗口): Tips: command-line窗口是一个特殊的窗口,在这种窗口中,你可以像编辑文本一样编辑命令,完成编辑后,按回车,就会执行此命令。你要重复执行某条命令,可以把光标移到该命令所在的行,然后按回车即可;你也可以对历史命令进行修改后再执行。详见“:help cmdline-window”。 接下来,在command-line窗口中输入以下命令: 这两条命令切换gdb的当前工作目录,并加载我们编译的sample程序准备调试。 现在使用VIM的移动命令,把光标移动到sample.c的第7行和14行,按“CTRL-B”在这两处设置断点,然后按“R”,使gdb运行到我们设置的第一个断点处(“CTRL-B”和“R”都是gdb_mappings.vim定义的键绑定,下面介绍的其它调试命令相同)。现在VIM看起来是这样: 断点所在的行被置以蓝色,并在行前显示标记1和2表明是第几个断点;程序当前运行到的行被置以黄色,行前以“=>”指示,表明这是程序执行的位置(显示的颜色用户可以调整)。 接下来,我们再按“C”,运行到第2个断点处,现在,我们输入下面的vim命令,在右下方分隔出一个名为gdb-variables的窗口: 然后用“v”命令选中变量i,按“CTRL-P”命令,把变量i加入到监视窗口,用同样的方式把变量result也加入到监视窗口,这里可以从监视窗口中看到变量i和result的值。 现在我们按“S”步进到factor函数,VIM会自动打开factor/factor.c文件并标明程序执行的位置。我们再把factor()函数中的变量n加入到监视窗口;然后按空格打开command-line窗口,输入下面的命令,把变量*r输入到变量窗口: 现在,VIM看起来是这样的: 现在,你可以用“S”、“CTRL-N”或“C”来继续执行,直至程序运行结束。 如果你是单步执行到程序结束,那么VIM最后可能会打开一个汇编窗口。是的,vimgdb支持汇编级的调试。这里我们不用进行汇编级调试,忽略即可。 如果你发现程序有错误,那么可以按“Q”退出调试(gdb会提示是否退出,回答y即可),然后修改代码、编译、调试,直到最终完成。在修改代码时,你可能并不喜欢vimgdb的键映射(例如,它把CTRL-B映射为设置断点,而这个键又是常用的翻页功能),你可以按<F7>取消vimgdb的键映射,或者你直接修改gdb_mappings.vim文件中定义的映射。 看,用VIM + GDB调试是不是很简单?! 我们可以再定制一下,使调试更加方便。 打开~/.vim/macros/ gdb_mappings.vim文件,在“let s:gdb_k = 0”这一行下面加上这段内容: 在“let s:gdb_k = 1”这一行下面加上这段内容: 注释掉最后一行的“call s:Toggle()”。 然后在你的vimrc中增加这段内容: 现在,在启动vim后,按<F7>,就进入调试模式、定义调试的键映射。在第一次进入调试模式时,会提示你输入要调试的文件名,以后就不必再输入了。再按一次<F7>,就退出调试模式,取消调试的键映射。 利用VIM的键映射机制,你可以把你喜欢的GDB命令映射为VIM的按键,方便多了。映射的例子可以参照~/.vim/macros/ gdb_mappings.vim。 再附上一张抓图,这是使用putty远程登录到linux上,在终端vim中进行调试。这也是我为什么喜欢vimgdb的原因,因为它可以在终端vim中调试,而clewn只支持gvim: 因为我不常使用GDB调试,所以本文仅举了个简单的例子,以抛砖引玉。欢迎大家共享自己的经验和心得。 最后,让我们感谢vimgdb作者xdegaye的辛勤劳动,我们下一篇会介绍clewn,这是VIM与GDB结合的另外一种形式,它和vimgdb同属一个项目。 [参考文档] 1. VIM帮助文件 2. http://vimcdoc.sourceforge.net/ 3. http://clewn.sourceforge.net/index.html [尾记] 本文可以自由应用于非商业用途。转载请注明出处。 原文链接:http://blog.csdn.net/easwy """""""""""""""""""""""""""""" " vimgdb setting """""""""""""""""""""""""""""" let g:vimgdb_debug_file = "" run macros/gdb_mappings.vim " easwy add call gdb("quit") " end easwy " easwy add if ! exists("g:vimgdb_debug_file") let g:vimgdb_debug_file = "" elseif g:vimgdb_debug_file == "" call inputsave() let g:vimgdb_debug_file = input("File: ", "", "file") call inputrestore() endif call gdb("file " . g:vimgdb_debug_file) " easwy end createvar *r :bel 20vsplit gdb-variables cd ~/tmp file sample :run macros/gdb_mappings.vim # ~/tmp/Makefile sample: sample.c factor/factor.c gcc -g -Wall -o sample sample.c factor/factor.c /* ~/tmp/factor/factor.c */ int factor(int n, int *r) { if (n <= 1) *r = n; else { factor(n - 1, r); *r *= n; } return 0; } /* ~/tmp/sample.c */ #include <stdio.h> extern int factor(int n, int *rt); int main(int argc, char **argv) { int i; int result = 1; for (i = 1; i < 6; i++) { factor(i, &result); printf("%d! = %d\n", i, result); } return 0; } :helptags ~/.vim/doc cd ~/install/vimgdb/ tar zxf vimgdb_runtime.tgz –C~/.vim/ PATH=$HOME/bin:$PATH export PATH make make install cd ~/install/ tar xjf vim-7.1.tar.bz2 tar xzf vimgdb71-1.12.tar.gz patch -d vim71 --backup -p0 < vimgdb/vim71.diff :help vimgdb Fun with strace and the GDB DebuggerLevel: Intermediate William B. Zimmerly (bill@zimmerly.com), Freelance Writer and Knowledge Engineer, Author 11 May 2006 Programming a UNIX® system can be fun as well as educational. With the UNIX strace tool and GDB, the GNU Project Debugger, you can really dig deep into the functionality of your system and learn a lot about the various programs that comprise it. Using both tools in concert can be a rewarding experience as you look under the hood of your UNIX machine. The UNIX family has always provided abundantly for its users. UNIX is a treasure chest of tools with which you can not only do productive work but also educate and entertain yourself as you explore the depths of the operating system. Two useful tools for this purpose are strace, with which you can trace the system calls of any program, and the GDB Debugger, which is a full-featured debugger that allows you to run programs in a controlled environment. The UNIX design consists of hundreds of function calls (called system calls) covering simple tasks, such as displaying a character string on the screen to setting task priorities. All UNIX programs accomplish their tasks by calling these low-level services that the operating system provides, and with the strace tool, you can actually see these calls and the parameters that they use. In this way, you can actually play with programs to learn about their low-level interactions with the operating system. Let the games begin Let's begin by examining a simple UNIX command -- pwd -- and then dive deeper into what the command does to accomplish its purpose. Launch an xterm to create a controlled environment to experiment with, and then type the command: $ pwd The pwd command displays the current working directory. The output on my computer at that moment in time was: /home/bill/ Such a simple function belies the complexity beneath the surface of the command (as do all computer programs, by the way). To get a picture of this complexity, run the pwd command again using the strace tool: $ strace pwd With that command, you can see just how much goes on in the UNIX machine to discover and list the current working directory you're in (see Listing 1). Listing 1: Output from the strace pwd command execve("/bin/pwd", ["pwd"], [/* 39 vars */]) = 0 The nuts and bolts of UNIX system calls It is beyond the scope of this article to go into too much detail about why all these system calls are necessary for retrieving and displaying the current working directory, but I will show how to obtain that information. In each line of Listing 1, a system call and its parameters are spelled out in a C-like format, just as a C programmer would expect to see them. Strace displays the calls like this as well, regardless of the actual programming language used in the creation of the program. If you want to understand all the details that appear in Listing 1, UNIX provides massive amounts of documentation on all the system calls. If there's a "most important" function in Listing 1, it is the getcwd() function, which stands for get current working directory. With the current xterm still showing the output of strace pwd, launch another xterm and type the following command to see what UNIX displays for this function: $ man getcwd What you should see is a complete listing of the getcwd() function and a listing of the arguments that this important C function requires and returns. Likewise, you can type man brk or man fstat64, and so on. These system functions are typically well documented, and if you take the time to study them, you will begin to understand just how powerful UNIX is and how easy it is to perform a detailed study of the low-level system. Of all operating systems, UNIX is best prepared to help you understand what goes on within it out of the box. Discover nweb For the next steps, you need something larger and more complex than a simple UNIX command like pwd. A simple Hypertext Transfer Protocol (HTTP) server, such as nweb, is perfect. An HTTP server listens for your browser requests when you're surfing the Internet, and then responds to your browser requests by sending the objects you're requesting, such as Web pages and graphics files. Download and install nweb, written by IBM developerWorks contributing writer, Nigel Griffiths. (See the Resources section for a link to Nigel's article, "nweb: a tiny, safe Web server (static pages only)" (developerWorks, June 2004).) After downloading es-nweb.zip to your $HOME/downloads directory, type the simple commands shown in Listing 2 to extract, compile, and launch the program: Note: I assume that you're going to compile this program to a Linux® workstation. If this is not the case, read the nweb article for details on compiling the program on other UNIX operating systems. Listing 2. Commands for extracting, compiling, and launching nweb $ cd src Note: The -ggdb option in Listing 2 differs from Nigel's article in that it tells the GCC compiler to optimize the program for debugging with the GDB Debugger, which you'll use later. Next, to verify that the nweb server is running, use the ps command shown in Listing 3 to check it. Listing 3. The ps command $ ps Finally, to verify that nweb is really running and that all is correct, launch a Web browser on your computer and type http://localhost:9090 in the address bar. Using strace with nweb Now, let's have some fun. Launch another xterm, and then use strace to trace the nweb server that is running. To do so, you must know the process ID of the program, and you must have the appropriate permissions. You'll watch only a specific set of system calls -- those related to networking. Begin by typing the command shown in the first line of Listing 4 (using the nweb process ID displayed above). You should see the output below (line 2 in Listing 4). Listing 4. Starting a trace of nweb $ strace -e trace=network -p 4009 Notice that the trace has stopped in the middle of a call to the network accept() function. Refresh the http://localhost:9090 page in your browser a few times, and notice what strace displays each time you refresh the page. Isn't that great? What you are watching is a low-level network call by the HTTP server, nweb, when it has been called by your Web browser. Simply put, nwebis accepting your browser's call. You can stop tracing the network calls on the running nweb process by pressing Ctrl+C when the xterm in which the strace is running has the window focus. On to the GDB Debugger As you saw, strace can be a great program for learning how user programs interact with the operating system through certain system calls. The GDB Debugger can also attach itself to a currently running process and help you to dig even deeper. The GDB Debugger is such a useful tool that a lot of information about it is available on the Internet. Debuggers in general are valuable tools, and anyone responsible for the development and upkeep of a computer system should know how to use them. So, while nweb is still running in another xterm session, halt the strace by pressing Ctrl+C, and then launch the GDB Debugger by typing the commands shown in Listing 5. Listing 5. Launch the GDB Debugger $ gdb --quiet The -quiet option tells the GDB Debugger to display only its prompt and not all the other startup information that it typically displays. If you want the extra text, leave the -quiet option off. The attach 4009 command starts debugging the currently running nweb server, and the GDB Debugger responds in kind by reading all the symbolic information about the process it can. Next, use the info command to list information about the program you're exploring (see Listing 6). Listing 6. The info command lists program information (gdb) info proc Another useful variant of the info command (see Listing 7) is info functions; however, the list of functions can be very long. Listing 7. Functions list from the info functions command (gdb) info functions Because you compiled the nweb program with the -ggdb option, a lot of debugging information was included in the executable, allowing the debugger to see the defined functions listed by file, as shown in Listing 7. The list and disassemble commands Two important GDB Debugger commands are list and disassemble. Try these commands by working with the code in Listing 8. Listing 8. The list command (gdb) list main As you can see, the list command lists the running program in source form with line numbers. Pressing the Return key (shown between lines 130 and 131) simply continues listing from where the last list left off. Now, try the disassemble command, which you can abbreviate to disass (see Listing 9). Listing 9. The disassemble command (gdb) disass main This disassembly listing shows the assembler-language listing of the main function. In this case, the assembler code indicates that the computer running the code has an Intel® Pentium® processor. Your code will look quite different if it's running on a computer with a different processor type, such as an IBM Power PC®-based computer. Watching it live Because you're watching an actual running program, you can set breakpoints, and then see the program as it replies to browser requests and transmits .html and .jpg files to the browser making the request. Listing 10 shows how you can do so. Listing 10. Set breakpoints (gdb) break 188 At this point, the GDB Debugger is set to break at the line where the nweb server accepts browser requests; the debugger will simply display the request and continue processing other requests without interrupting the running program. Refresh the http://localhost:9090/ page in your browser a few times, and watch the GDB Debugger display the breakpoint and continue running. While refreshing the browser page, you should see breakpoint information, such as that shown in Listing 11, scrolling in the GDB Debugger xterm. Just as with strace, you can stop debugging the nweb server by pressing Ctrl+C. After stopping the tracing, you can exit the GDB Debugger by typing the quit command. Listing 11. Breakpoint information in the GDB Debugger xterm Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188 Notice that you're telling the GDB Debugger to quit debugging a program that is still active in memory. Even after leaving the debugger, you can refresh the browser page and see that nweb is still running. You can halt the program by typing the kill 4009 command, or the page will disappear when you leave your session. As always, you can learn a lot about tools, such as strace and the GDB Debugger, from their man and info pages. Be sure to use the tools that UNIX provides you! Learn as much as you can It's never a bad thing to learn as much as you can about the computer that you're working on, nor is it bad to have fun in the process. UNIX actually encourages you to explore and learn by providing tools, such as strace and the GDB Debugger, as well as a wealth of information in the man and info pages. Computers are an extension of our minds, and the more we know about them, the more useful they become. Resources Learn
Get products and technologies
Discuss
About the author Bill Zimmerly is a knowledge engineer, a low-level systems programmer with expertise in various versions of UNIX and Microsoft® Windows®, and a free thinker who worships at the altar of Logic. Bill is also known as an unreasonable person. Unreasonable as in, "Reasonable people adapt themselves to the world. Unreasonable people attempt to adapt the world to themselves. All progress, therefore, depends on unreasonable people" (George Bernard Shaw). Creating new technologies and writing about them are his passions. He resides in rural Hillsboro, Missouri, where the air is fresh, the views are inspiring, and good wineries are all around. There's nothing quite like writing an article on UNIX shell scripting while sipping on a crystal-clear glass of Stone Hill Blush. You can contact him at bill@zimmerly.com. [译文] strace --- 系统调用跟踪与信号报告工具原文出处:http://linuxgazette.net/148/saha.html 原文作者:Amit Kumar Saha and Sayantini Ghosh 翻译时间:2008年3月3日 译者:王旭 <gnawux (at) gmail.com> 译者按:翻译篇文章换换心情,strace 是个有用的工具,LinuxGazette 是个不错的杂志,本文也希望你喜欢。 了解一切是怎么运转的是件有趣的事情。所有的C程序员们都知道,在他们的C程序的“输入-处理-输出”周期中用到了很多系统调用 。 看看程序中到底哪些系统调用被用到了无疑是件很让人兴奋的事情。本文就是关于这个话题的,让我们开始吧。 何为 ’strace’? ’strace’ 是一个用于在运行时跟踪进程调用的系统调用的工具。它同时报告进程收到的信号(或软中断) 。 根据手册页,在最简单的情况下,“strace 运行指定的命令直到该命令执行完成。它截获并记录进程调用的系统调用和收到的信号。” 直接在终端中敲入”strace”命令就可以看到它的各个开关和选项: $ strace 跟踪系统调用 先从一个简单的例子开始。考虑如下C代码(清单1): /* Listing 1*/ 假设编译好的目标文件名为’temp.o’。如下运行: $strace ./temp.o 将会得到如下跟踪结果输出: execve("./temp.o", ["./temp.o"], [/* 36 vars */]) = 0 现在,我们来理论联系实际一下。 我们知道,当用户输入命令或可执行文件来运行的时候,系统会制造一个“子”Shell,并用这个子Shell来运行程序。这是通过系统调用”execve”来实现的。因此,跟踪结果是以如下内容开始的: execve("./temp.o", ["./temp.o"], [/* 36 vars */]) = 0 接下来,进程调用 ‘brk()’, ‘open’,'access’, ‘open’, ‘close’ 知道最终进程从 shell 中分离并使用”exit_group(0)”推出。 如上,跟踪过程显示了系统调用及其返回值。 strace的信号报告功能 下面来看一下”strace”的信号报告功能。考虑如下C代码(列表2): /*Listing 2*/ 假设编译输出的可执行文件为”temp-1.o”。如下运行: $ strace -o trace.txt ./temp-1.o 这里,”-o”开关将把跟踪结果保存到”trace.txt”文件。 这里,你将会看到”write()”系统调用将不停地被调用,现在,用”ctrl-c”来结束这个进程 [[[...]]] 现在,查看”trace.txt” $cat trace.txt 其中的最后几行应该是: --- SIGINT (Interrupt) @ 0 (0) --- 因为我们使用”ctrl-c”结束了进程,于是信号 SIGINT 被发送给进程 ,正如”strace”所输出的一样。 收集系统调用相关的统计信息 使用”strace”,还可以对跟踪的系统调用进行一些简单的统计。这通过”-c”开关实现。例如: $ strace -o trace-1.txt -c ./temp-1.o # 运行上述可执行程序 'temp-1.o' 如上,在其他输出信息之外,同时还输出了系统调用的统计信息,”write()”系统调用(一共运行了46702次),花去了进程的绝大部分时间(100%)。 后记 本文对”strace”的一些基本功能进行了简单的介绍。这个工具在进行可执行程序的bug寻找、寻找程序崩溃点的时候非常有用。使用 “strace”,可以极大地缩小问题可能发生的范围。 与 ‘GNU Debugger’ (gdb) 和 ‘ltrace’一起,”strace” 为 Linux 程序员提供了强大的调试能力。 有用链接: strace命令详解strace 命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用。 strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核。 下面记录几个常用 option . 1 -f -F选项告诉strace同时跟踪fork和vfork出来的进程 2 -o xxx.txt 输出到某个文件。 3 -e execve 只记录 execve 这类系统调用 ————————————————— 进程无法启动,软件运行速度突然变慢,程序的"SegmentFault"等等都是让每个Unix系统用户头痛的问题, 本文通过三个实际案例演示如何使用truss、strace和ltrace这三个常用的调试工具来快速诊断软件的"疑难杂症"。 truss和strace用来跟踪一个进程的系统调用或信号产生的情况,而 ltrace用来跟踪进程调用库函数的情况。truss是早期为System V R4开发的调试程序,包括Aix、FreeBSD在内的大部分Unix系统都自带了这个工具; 而strace最初是为SunOS系统编写的,ltrace最早出现在GNU/DebianLinux中。 这两个工具现在也已被移植到了大部分Unix系统中,大多数Linux发行版都自带了strace和ltrace,而FreeBSD也可通过Ports安装它们。 你不仅可以从命令行调试一个新开始的程序,也可以把truss、strace或ltrace绑定到一个已有的PID上来调试一个正在运行的程序。三个调试工具的基本使用方法大体相同,下面仅介绍三者共有,而且是最常用的三个命令行参数: -f :除了跟踪当前进程外,还跟踪其子进程。 -o file :将输出信息写到文件file中,而不是显示到标准错误输出(stderr)。 -p pid :绑定到一个由pid对应的正在运行的进程。此参数常用来调试后台进程。 使用上述三个参数基本上就可以完成大多数调试任务了,下面举几个命令行例子: truss -o ls.truss ls -al: 跟踪ls -al的运行,将输出信息写到文件/tmp/ls.truss中。 strace -f -o vim.strace vim: 跟踪vim及其子进程的运行,将输出信息写到文件vim.strace。 ltrace -p 234: 跟踪一个pid为234的已经在运行的进程。 三个调试工具的输出结果格式也很相似,以strace为例: brk(0) = 0×8062aa8 brk(0×8063000) = 0×8063000 mmap2(NULL, 4096, PROT_READ, MAP_PRIVATE, 3, 0×92f) = 0×40016000 每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。 truss、strace和ltrace的工作原理大同小异,都是使用ptrace系统调用跟踪调试运行中的进程,详细原理不在本文讨论范围内,有兴趣可以参考它们的源代码。 举两个实例演示如何利用这三个调试工具诊断软件的"疑难杂症": 案例一:运行clint出现Segment Fault错误 操作系统:FreeBSD-5.2.1-release clint是一个C++静态源代码分析工具,通过Ports安装好之后,运行: # clint foo.cpp Segmentation fault (core dumped) 在Unix系统中遇见"Segmentation Fault"就像在MS Windows中弹出"非法操作"对话框一样令人讨厌。OK,我们用truss给clint"把把脉": # truss -f -o clint.truss clint Segmentation fault (core dumped) # tail clint.truss 739: read(0×6,0×806f000,0×1000) = 4096 (0×1000) 739: fstat(6,0xbfbfe4d0) = 0 (0×0) 739: fcntl(0×6,0×3,0×0) = 4 (0×4) 739: fcntl(0×6,0×4,0×0) = 0 (0×0) 739: close(6) = 0 (0×0) 739: stat("/root/.clint/plugins",0xbfbfe680) ERR#2 'No such file or directory' SIGNAL 11 SIGNAL 11 Process stopped because of: 16 process exit, rval = 139 我们用truss跟踪clint的系统调用执行情况,并把结果输出到文件clint.truss,然后用tail查看最后几行。 注意看clint执行的最后一条系统调用(倒数第五行):stat("/root/.clint/plugins",0xbfbfe680) ERR#2 'No such file or directory',问题就出在这里:clint找不到目录"/root/.clint/plugins",从而引发了段错误。怎样解决?很简单: mkdir -p /root/.clint/plugins,不过这次运行clint还是会"Segmentation Fault"9。继续用truss跟踪,发现clint还需要这个目录"/root/.clint/plugins/python",建好这个目录后 clint终于能够正常运行了。 案例二:vim启动速度明显变慢 操作系统:FreeBSD-5.2.1-release vim版本为6.2.154,从命令行运行vim后,要等待近半分钟才能进入编辑界面,而且没有任何错误输出。仔细检查了.vimrc和所有的vim脚本都没有错误配置,在网上也找不到类似问题的解决办法,难不成要hacking source code?没有必要,用truss就能找到问题所在: # truss -f -D -o vim.truss vim 这里-D参数的作用是:在每行输出前加上相对时间戳,即每执行一条系统调用所耗费的时间。我们只要关注哪些系统调用耗费的时间比较长就可以了,用less仔细查看输出文件vim.truss,很快就找到了疑点: 735: 0.000021511 socket(0×2,0×1,0×0) = 4 (0×4) 735: 0.000014248 setsockopt(0×4,0×6,0×1,0xbfbfe3c8,0×4) = 0 (0×0) 735: 0.000013688 setsockopt(0×4,0xffff,0×8,0xbfbfe2ec,0×4) = 0 (0×0) 735: 0.000203657 connect(0×4,{ AF_INET 10.57.18.27:6000 },16) ERR#61 'Connection refused' 735: 0.000017042 close(4) = 0 (0×0) 735: 1.009366553 nanosleep(0xbfbfe468,0xbfbfe460) = 0 (0×0) 735: 0.000019556 socket(0×2,0×1,0×0) = 4 (0×4) 735: 0.000013409 setsockopt(0×4,0×6,0×1,0xbfbfe3c8,0×4) = 0 (0×0) 735: 0.000013130 setsockopt(0×4,0xffff,0×8,0xbfbfe2ec,0×4) = 0 (0×0) 735: 0.000272102 connect(0×4,{ AF_INET 10.57.18.27:6000 },16) ERR#61 'Connection refused' 735: 0.000015924 close(4) = 0 (0×0) 735: 1.009338338 nanosleep(0xbfbfe468,0xbfbfe460) = 0 (0×0) vim试图连接10.57.18.27这台主机的6000端口(第四行的connect()),连接失败后,睡眠一秒钟继续重试(第6行的 nanosleep())。以上片断循环出现了十几次,每次都要耗费一秒多钟的时间,这就是vim明显变慢的原因。可是,你肯定会纳闷:"vim怎么会无缘无故连接其它计算机的6000端口呢?"。问得好,那么请你回想一下6000是什么服务的端口?没错,就是X Server。看来vim是要把输出定向到一个远程X Server,那么Shell中肯定定义了DISPLAY变量,查看.cshrc,果然有这么一行:setenv DISPLAY ${REMOTEHOST}:0,把它注释掉,再重新登录,问题就解决了。 案例三:用调试工具掌握软件的工作原理 操作系统:Red Hat Linux 9.0 用调试工具实时跟踪软件的运行情况不仅是诊断软件"疑难杂症"的有效的手段,也可帮助我们理清软件的"脉络",即快速掌握软件的运行流程和工作原理,不失为一种学习源代码的辅助方法。下面这个案例展现了如何使用strace通过跟踪别的软件来"触发灵感",从而解决软件开发中的难题的。 大家都知道,在进程内打开一个文件,都有唯一一个文件描述符(fd:file descriptor)与这个文件对应。而本人在开发一个软件过程中遇到这样一个问题: 已知一个fd,如何获取这个fd所对应文件的完整路径?不管是Linux、FreeBSD或是其它Unix系统都没有提供这样的API,怎么办呢?我们换个角度思考:Unix下有没有什么软件可以获取进程打开了哪些文件?如果你经验足够丰富,很容易想到lsof,使用它既可以知道进程打开了哪些文件,也可以了解一个文件被哪个进程打开。好,我们用一个小程序来试验一下lsof,看它是如何获取进程打开了哪些文件。lsof: 显示进程打开的文件。 /* testlsof.c */ #include #include #include #include #include int main(void) { open("/tmp/foo", O_CREAT|O_RDONLY); /* 打开文件/tmp/foo */ sleep(1200); /* 睡眠1200秒,以便进行后续操作 */ return 0; } 将testlsof放入后台运行,其pid为3125。命令lsof -p 3125查看进程3125打开了哪些文件,我们用strace跟踪lsof的运行,输出结果保存在lsof.strace中: # gcc testlsof.c -o testlsof # ./testlsof & [1] 3125 # strace -o lsof.strace lsof -p 3125 我们以"/tmp/foo"为关键字搜索输出文件lsof.strace,结果只有一条: # grep '/tmp/foo' lsof.strace readlink("/proc/3125/fd/3", "/tmp/foo", 4096) = 8 原来lsof巧妙的利用了/proc/nnnn/fd/目录(nnnn为pid):Linux内核会为每一个进程在/proc/建立一个以其pid为名的目录用来保存进程的相关信息,而其子目录fd保存的是该进程打开的所有文件的fd。目标离我们很近了。好,我们到/proc/3125/fd/看个究 竟: # cd /proc/3125/fd/ # ls -l total 0 lrwx—— 1 root root 64 Nov 5 09:50 0 -> /dev/pts/0 lrwx—— 1 root root 64 Nov 5 09:50 1 -> /dev/pts/0 lrwx—— 1 root root 64 Nov 5 09:50 2 -> /dev/pts/0 lr-x—— 1 root root 64 Nov 5 09:50 3 -> /tmp/foo # readlink /proc/3125/fd/3 /tmp/foo 答案已经很明显了:/proc/nnnn/fd/目录下的每一个fd文件都是符号链接,而此链接就指向被该进程打开的一个文件。我们只要用readlink()系统调用就可以获取某个fd对应的文件了,代码如下: #include #include #include #include #include #include int get_pathname_from_fd(int fd, char pathname[], int n) { char buf[1024]; pid_t pid; bzero(buf, 1024); pid = getpid(); snprintf(buf, 1024, "/proc/%i/fd/%i", pid, fd); return readlink(buf, pathname, n); } int main(void) { int fd; char pathname[4096]; bzero(pathname, 4096); fd = open("/tmp/foo", O_CREAT|O_RDONLY); get_pathname_from_fd(fd, pathname, 4096); printf("fd=%d; pathname=%sn", fd, pathname); return 0; } 出于安全方面的考虑,在FreeBSD 5 之后系统默认已经不再自动装载proc文件系统,因此,要想使用truss或strace跟踪程序,你必须手工装载proc文件系统:mount -t procfs proc /proc;或者在/etc/fstab中加上一行: proc /proc procfs rw 0 0 重定向 stderr 和 stdout 输出到 gdb 窗口级别: 初级 恽 益群 (yunyiqun@cn.ibm.com), 软件工程师, IBM 余 锦 (yujin@cn.ibm.com)IBM CDL 2008 年 3 月 20 日 本文介绍了一个实用 gdb 调试技巧。 它结合实际例子,一步一步示意如何重定向 stderr 和 stdout 到 gdb窗口,使得查看应用程序的输出信息更为方便,从而提高调试者的工作效率。 简介 本文介绍了一个实用 gdb 调试技巧。 它结合实际例子,一步一步示意如何重定向 stderr 和 stdout 到 gdb窗口,使得查看应用程序的输出信息更为方便,从而提高调试者的工作效率。 问题 为了调试基于 Eclipse 的 Java 和 C++ 混合的应用程序时,通常同时使用 Eclipse 和 gdb 来分别调试 Java 和 C++ 代码。此时,被调试程序的标准输出( stdout )和标准错误输出( stderr )取决于这个该程序的启动方式。如果程序是在 Eclipse 的 IDE 环境下启动的,那默认情况下 stderr 和 stdout 都会输出在 Eclipse 的 console 窗口下,如果这时又需要用 gdb 来调试 C++ 的代码,那为了查看输出的调试信息,还不得不切换到另外一个窗口(比如 Eclipse 的 console窗口)去查看,然后再切回来继续调试,这是不是很不方便呢? 解决之道 下面本文将介绍个一个简单的方法用以重定向 stderr 和 stdout 到指定的目的地,包括正在使用的 gdb 窗口。 由于这个方法是基于 gdb 提供的基本而又强大的两个功能之上的,所以在介绍它之前,先简单介绍一下 gdb 的这两个功能。 使用 call 命令调用外部函数 GDB 提供的 call 命令允许调试者在当前函数调用栈的栈顶调用函数,犹如在被调试的程序中执行的一般。比如想关闭某个文件(文件描述符为 fd ),那只需要在 gdb 中输入: (gdb) call (int)close(fd) 有了它,gdb 就可以具有很强大的功能,因为只要把所需要的功能写成一个函数编译进应用程序,调试时候在 gdb 中 call 该函数便可。 利用 .gdbinit 来自定义 gdb 命令 GDB 在启动的时候会按一定的路径顺序(通常是先当前目录而后用户目录)寻找 .gdbinit 文件,一旦找到,就会自动执行里面的命令。这个功能允许用户把常用的一些命令放在这个文件里,这样就不用每次进入 gdb 后再去手动执行这些命令。事实上,.gdbinit 就是一个脚本,甚至可在里面把常用的若干 gdb命令序列定义成一个新命令,这样只要在 gdb 里面输入这个新命令就等于自动执行了被定义的那个命令序列。 另外,如果用户已经在 gdb 里后,再去修改 .gdbinit ,只要通过: (gdb) source ~/.gdbinit 便可以让那些新增加的改动生效。 重定向 stdout和 stderr 首先,打开一个终端窗口,先用 ps 命令查到所需进程的 pid 。 $ ps ax | grep HelloWorld(要调试的应用程序名) 上面列出的第一行中的13522,就是 HelloWorld 的 pid 。 接着,我们使 gdb 连接上这个应用程序。如下: $ gdb –pid=13522 或者可以先运行 gdb ,然后用 attach 命令,如下: $gdb 不出意外的话,接下来就可以在 gdb 窗口调试了。 可以试一下看看是不是输出信息不在 gdb 窗口中。 (gdb) call (void)printf(“\n Is this string printed on gdb window\n”) 这时在 gdb 窗口是看不见这个输出的。 为了跟踪查看某些重要的调试信息,得不停地切换到别的窗口去看,很不方便。 解决的方法如下: 先关闭 stdout ,和 stderr 对应的文件描述符。 (gdb) call (int)close(1) 然后使用以下命令查看一下当前 gdb 窗口所在的虚拟终端。 (gdb) shell tty 这时再重新打开 stdout 和 stderr , 把它们和 gdb 窗口所在的虚拟终端关联起来。 (gdb) p (int)open("/dev/ttyp1", 2) 如果这两个命令执行结果不是如上结果(1和2),意味着 open 执行失败,需要重新进行 close 和 open. 另外,如果把这里的 ”/dev/ttyp1” 替换成目标文件名,便可将 stderr 和 stdout 重定向到该文件。 接下来,重新执行如下命令: (gdb) call (void)printf(“\n Is this string be printed on gdb window?\n”) 这次输出到了 gdb 窗口,也证明成功重定向了被调试程序的 stdout和 stderr . 以后就可直接在 gdb 窗口中看到所有的输出信息,勿需再切换窗口。 如果每次都要运行这么多命令,还是较为繁琐,此时可以利用 .gdbinit 来简化用户输入:把这一系列命令定义一个新命令,放到 .gdbinit 文件里,然后在 gdb 里执行这个命令便可。 关于在 .gdbinit 中定义 gdb 新命令的语法可以参考 dW 上其他的文章。下面就针对重定向问题,看 .gdbinit 是如何通过引入新命令来简化用户输入。其实, 只需在 .gdbinit 文件里,增加如下脚本: def redirect 上面这段脚本定义了一个新命令: redirect ,就是重定向的意思。 之后,当重启 gdb (或者运行 source~/.gdbinit ),并且连接到要调试的应用程序后. 用以下简单的两步就可达到重定向的目的: 第一步,仍是得到这个 gdb 窗口所在的虚拟终端: (gdb)shell tty 接着就可以调用 .gdbinit 中定义的命令了: (gdb)redirect("/dev/ttyp3") 为了易于理解记忆,甚至可以按如下方式为该命令增加帮助信息。 把下面这段脚本紧接添加到刚才 redirect 所对应的 end 后面 document redirect 这样,在 gdb 窗口中,就可以使用 help 命令来查看这个命令的帮助信息了: (gdb) help redirect 即时刷新 stdout 和 stderr 由于系统有时会对输入输出会进行缓存,因此有可能会碰到如下情况:执行了输出语句,但还是不见输出。尤其是在调试 Java/C++ 混合程序时容易发生这种情况,比如在 JNI 代码中的printf 就很可能被缓存后再输出。这种系统缓存机制很不利于调试程序,因为调试信息的及时输出很重要,很多时候要利用这些输出信息来判断程序的行为是否正常。 为了解决这个问题,可以调用 fflush: (gdb)call (int)fflush(0) 这样所有的缓冲都会得到立刻刷新,包括 stdout 和 stderr . 调试者就能马上看到前面执行的输出的结果。 总结 gdb的命令很丰富,功能也很多,巧妙地利用它们可以大大地提高调试者的工作效率。本文通过利用 gdb 的 call 命令和 .gdbinit 文件的扩展功能,提出了一个简单有效的方法, 用以重定向被调试程序的 stdout 和 stderr ,以及如何及时刷新显示输出结果。 参考资料
作者简介 恽益群,IBM 中国软件开发中心软件工程师,东南大学硕士。专长基于 MAC OS X/Linux的应用程序开发, GUI 应用程序移植等。 余锦,IBM 中国软件开发中心高级软件工程师,清华大学硕士。专长基于MAC OS X/Linux的应用程序开发, GUI 应用程序移植等。 Gdbserver远程调试的具体实现实用技巧:Gdbserver远程调试的具体实现 发布时间:2008.05.22 08:50 来源:赛迪网 作者:korn 采用的是nfs目标板挂载本机目录的方法,当然首先,你得开通本机的nfs共享服务,具体步骤如下: 1、进入/etc目录,vim exports这个文件,在里面添加/home 192.168.0.*(rw,sync)保存后退出 注:/home 为要共享的文件夹的名称,192.168.0.*为本NFS服务器允许访问的客户端ip,若nfs不成功,后面参数rw ro 等标志对文件夹操作权限,sync:数据同步写入内存和硬盘,也可以使用async,此时数据会先暂存于内存中,而不立即写入硬盘。可以将括号里面的sync去掉。 2、重新启动nfs服务,命令为:/sbin/service nfs restart 3、输入命令route del default来关闭网关(加快mount速度) 4、mount 192.168.0.47(为本机的ip):/home /mnt用以测试本机是否开通nfs服务。 cd /mnt目录下,看mnt下的内容是否与home的内容一致,若一致,表明已经开通nfs服务。 当本机的nfs服务开通后,你还需要配置开发板的ip地址,由于各个开发板ip地址配置方法不一样,所以,假设开发板的ip地址为192.168.2.100. 搭建交叉编译环境的步骤: 1、一般在安装linux时候,自动安装c编译环境,因此不需要再重新安装gcc编译器。 2、安装交叉编译器 从ftp://ftp.arm.linux.org.uk/pub/armlinux/toolchain/下载交叉编译器cross-3.2.tar.bz2,存放在/usr/local目录下。 切换致该目录: # cd /usr/local # mkdir arm 然后解压cross-3.2.tar.bz2: # tar jxvf cross-3.2.tar.bz2 –C /usr/local/arm 解压后把/usr/local/arm/usr/local/arm中最后一个arm拷贝到/usr/local,也就是用命令cd /usr/local/arm/usr/local中,用cp -a arm /usr/local把arm拷贝到/usr/local中去。 3、把交叉编译器的路径加入到PATH。(两种方法a,b) a、# export PATH=$PATH:/usr/local/arm/bin 注:(这只能在当前的终端下才是有效的,) b、修改/etc/profile 文件: # vim /etc/profile 增加路径设置,在末尾添加如下: export PATH=$PATH:/usr/local/arm/bin 4、使新的环境变量生效。 # source /etc/profile 5、检查是否将路径加入PATH的方法。 # echo $PATH 如果显示的内容中有/usr/local/arm/bin,说明已经将交叉编译器的路径加入PATH。自此,交叉编译环境安装完成。 6、测试。 下面我们就来测试一个简单的例子。 /*Hello.c*/ #include int main() { printf(“hello word!\n”); return 0; } 程序输好以后确认无误,保存。进入程序文件所在目录 # arm-linux-gcc hello.c –o hello (-o 可以理解为“目标为生成”)arm-linux-gcc是第一次出现,有人可能会问这个哪里来的,不妨打开刚才安装的交叉编译工具目录/usr/local/arm-linux/arm-linux/bin/可以发现里面有一个arm-linux-gcc文件,这个就是针对arm的CPU的gcc编译器了。以后用其它编译工具链式也可以通过这种方法看看其编译器是什么了。编译好了以后就可以下载到目标机进行测试了。当然也可以先在PC机上测试正误。用gcc hello.c –o hello就可以生成PC机上程序了,在运行./hello 就可以发现终端显示hello!字样。用arm-linux-gcc编译的程序在PC机上是不能运行的,运行后给出错误报告:无法执行二进制文件。说明经过交叉编译环境编译出的文件是硬件可执行的二进制代码文件. 7、交叉编译环境搭建成功。 要经行gdbserver远程调试,还必须安装gdb远程调试工具: gdb的源代码包可以从http: //ftp.cs.pu.edu.tw/Linux/sourceware/gdb/releases/下载,最新版本为gdb-6.4。下载到某个目录,笔者下载到自己的用户目录:/home/vicky。 下载完后,进入/home/vicky目录,配置编译步骤如下: #tar jxvf gdb-6.4-tar-bz2 #cd gdb-6.4 #./configure --target=arm-linux --prefix=/usr/local/arm-gdb -v #make (这一步的时候可能会有问题,提示一个函数中(具体函数名不记得了)parse error,就是unsigned前边多了一个”}”,你用vi进入那一行把它删掉就行了。一般都不会出错的。) #make install #export PATH=$PATH:/usr/local/arm-gdb 进入gdbserver目录: #./configure --target=arm-linux –host=arm-linux #make CC=/usr/local/arm/bin/arm-linux-gcc (这一步要指定arm-linux-gcc的位置,可能跟你的不一样) 没有错误的话就在gdbserver目录下生成gdbserver可执行文件,把它烧写到flash的根文件系统分区,或通过nfs mount的方式都可以。只要保证gdbserver能在开发板上运行就行。 下面就可以用gdb+gdbserver调试我们开发板上的程序了。在目标板上运行gdbserver,其实就是在宿主机的minicom下,我的red hat linux装在vmware下的。我是在minicom下#mount 192.168.2.100:/ /tmp后做的(这里参数-o nolock可以不加,不加这一步执行得反而更快些),hello和gdbserver都是位于linux根目录下,把主机根目录挂在到开发板的/tmp 目录下。 要进行gdb调试,首先要在目标系统上启动gdbserver服务。在gdbserver所在目录下输入命令: (minicom下) #cd /tmp #./gdbserver 192.168.2.100:2345 hello 192.168.2.100为宿主机IP,在目标系统的2345端口开启了一个调试进程,hello为要调试的程序。 出现提示: Process /tmp/hello created: pid="80" Listening on port 2345 (另一个终端下) #cd / #export PATH=$PATH:/usr/local/arm-gdb/bin #arm-linux-gdb hello (gdb) target remote 192.168.2.223:2345 (192.168.2.223为开发板IP) 出现提示: Remote debugging using 192.168.2.223:2345 [New thread 80] [Switching to thread 80] 0x40002a90 in ??() 同时在minicom下提示: Remote debugging from host 192.168.2.100 (gdb) 连接成功,这时候就可以输入各种gdb命令如list、run、next、step、break等进行程序调试了。 注:在远程调试的这些步骤中nfs配置和安装交叉编译工具,以及安装gdb工具的没有什么顺序,只是主意再gdbserver远程连接前要配置好开发板的ip地址。 (责任编辑:云子) 12月13日 一致性与可用性:Werner Vogels谈最终一致性一致性与可用性:Werner Vogels谈最终一致性作者 Sadek Drobi译者 郭晓刚 发布于 2008年1月17日 下午7时58分
直到90年代中期,当谈到数据复制的时候,分布的透明性和数据的一致性常常都是重要的工作目标。随着大型Internet系统开始崛起,可用性(availability)也成了另一项重要的考虑因素。 Eric Brewer提出的CAP定理说,“在数据共享的系统的三项属性当中,数据一致性、系统可用性和对网络分区的耐受性,在任何给定时间内都只能达成其中的两项”。由于“在较大分布规模的系统中,网络分区是给定的”,因此一致性和可用性必有一项需要放宽。 在此前提下,最终一致性(eventual consistency)的概念开始赢得关注。与他在QCon London 2007上的演讲一脉相承,Werner Vogels最近在博客上总结了一些与大规模数据复制及一致性需求相关的原则、抽象和权衡。 他强调说一致性并不是绝对优先考虑的事: 不一致是可以容忍的,这有两个理由:一是可以在高并发条件下提高读写性能;二是处理一些分区状况——多数决模型(majority model)有可能使系统的一部分表现为不可用,虽然那些节点正运行良好。 不一致是否可接受取决于客户应用程序。Vogels给出了一个网站的例子,例中真正重要的是“用户感知到的一致性”,也就是让不一致窗口——即“更 新发生时刻到任何观察者都一定能观察到更新后数据的时刻之间的时间段”——“小于顾客对下一页面加载时间的期待”,这样更新就可在预期发生下一次读取的时 刻之前传播到整个系统。 如果换成更浅显的语言,Vogels说,“看待一致性有两种角度”: 一种是从开发者/客户端的角度;他们如何观察数据更新。第二种是从服务器的角度;更新如何流经整个系统,系统对更新有何保证。 在客户端,Vogels列举了四个角色:一个被观察者看作是“黑盒”的存储系统,而观察者由三个进程来扮演:“进程A[……]对存储系统进行读写 ”,“进程B和进程C[……]独立于进程A,它们也读写存储系统”。这些进程是“独立的,并且需要相互通信以共享信息。”客户端一致性就在于“一个观察者 (在此即进程A、B或C)如何以及何时看到存储系统中的一个数据对象被更新。” 一致性有不同程度:
Vogels还概要说明了最终一致性模型的各种变体:
在服务器端,关注的是如何达到一致性和可用性的程度要求。Vogels举出了各种场景,其中“N是保存数据副本的节点数量,W是在更新完成之前需要确认收到更新的副本数量,R是当通过一次读操作访问一个数据对象时要联系的副本数量”。 查看英文原文:Consistency vs. availability: eventual consistency by Werner Vogels |
|||||||||||||||||||||||||||||||||||||||||
|
|