kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

强制内联和强制不内联

1.强制不内联

一个函数,如果代码量比较少的话,用 -O3优化开关的话,gcc有可能将这个函数强制内联(inline)即使,你在函数前没有写inline助记符。
如果是一个手写汇编的函数,那样的话很有可能破坏参数。gcc里有强制不内联的,用法如下

1
void foo() __attribute__((noinline));

但是有的gcc可能会忽略 noinline。
那么你可以将你实现的这个函数写到调用函数之后,就不会被inline了。这是因为编译器gcc只内联当前函数之前可见(实现代码在前)的函数。

2.优化时无法识别inline函数中的ASM汇编

当GCC尝试内联一个函数时,如果该函数中存在内联汇编,则该汇编语句块可能被丢弃;

1
2
3
4
5
6
7
8
9
10
11
12
13
__inline__ __attribute__((always_inline))int Increment(int volatile *add, int inc)
{
    int res;
    __asm__
    (
    "lock \n\t"
    "xaddl %0,(%1)\n\t"
    :"=r"(res)
    :"r"(add),"0"(inc)
    :"memory"
    );
    return res;
}

kdump el5 --dump-dmesg 错误

原因:

http://vault.centos.org/5.11/os/SRPMS/kexec-tools-1.102pre-165.el5.src.rpm
这个包的一个patch(kexec-tools-1.102pre-makedumpfile-dump-dmesg.patch)是为了得到dmesg的,
但是它判断dmesg的结束是用logged_chars(看kernel/printk.c),logged_chars应该是输出的结束,所以不对。
改成log_end就行,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
diff --git a/kexec-tools-1.102pre-makedumpfile-dump-dmesg.patch b/kexec-tools-1.102pre-makedumpfile-dump-dmesg.patch
index 3938280..76c402a 100644
--- a/kexec-tools-1.102pre-makedumpfile-dump-dmesg.patch
+++ b/kexec-tools-1.102pre-makedumpfile-dump-dmesg.patch
@@ -68,7 +68,7 @@ diff -up kexec-tools-testing-20070330/makedumpfile/makedumpfile.c.orig kexec-too
 +dump_dmesg()
 +{
 +      int log_buf_len, length_log, length_oldlog, ret = FALSE;
-+      unsigned long log_addr, logged_chars, index;
++      unsigned long log_addr, logged_chars, log_end, index;
 +      char *log_buffer = NULL;
 +
 +      if (!open_files_for_creating_dumpfile())
@@ -101,10 +101,15 @@ diff -up kexec-tools-testing-20070330/makedumpfile/makedumpfile.c.orig kexec-too
 +               printf("Failed to get logged_chars.\n");
 +               return FALSE;
 +      }
++      if (!readmem(VADDR, SYMBOL(log_end), &log_end, sizeof(log_end))) {
++               printf("Failed to get log_end.\n");
++               return FALSE;
++      }
 +      DEBUG_MSG("\n");
 +      DEBUG_MSG("log_addr     : %lx\n", log_addr);
 +      DEBUG_MSG("log_buf_len  : %d\n", log_buf_len);
 +      DEBUG_MSG("logged_chars : %ld\n", logged_chars);
++      DEBUG_MSG("log_end      : %ld\n", log_end);
 +
 +      if ((log_buffer = malloc(log_buf_len)) == NULL) {
 +               ERRMSG("Can't allocate memory for log_buf. %s\n",
@@ -112,21 +117,16 @@ diff -up kexec-tools-testing-20070330/makedumpfile/makedumpfile.c.orig kexec-too
 +               return FALSE;
 +       }
 +
-+      if (logged_chars < log_buf_len) {
++      if (log_end < log_buf_len) {
 +               index = 0;
-+               length_log = logged_chars;
++               length_log = log_end;
 +
 +               if(!readmem(VADDR, log_addr, log_buffer, length_log)) {
 +                        printf("Failed to read dmesg log.\n");
 +                        goto out;
 +               }
 +      } else {
-+               if (!readmem(VADDR, SYMBOL(log_end), &index, sizeof(index))) {
-+                        printf("Failed to get log_end.\n");
-+                        goto out;
-+               }
-+               DEBUG_MSG("log_end      : %lx\n", index);
-+               index &= log_buf_len - 1;
++               index = log_end & (log_buf_len - 1);
 +               length_log = log_buf_len;
 +               length_oldlog = log_buf_len - index;
 +


如果不修改上面bug,kdump得到vmcore后用 makedumpfile –dump-dmesg 无法解得dmesg,补救办法如下:

kdump得到vmcore后
1、vmlinux没有debuginfo,crash不能运行
2、makedumpfile -F –dump-dmesg vmcore > dmesg 只能显示开头一下部分dmesg (不懂为什么)
解决:

方法一、

通过/boot/System.map 或者 /proc/kallsyms 找到 log_buf 地址,例如 0xffffffff81a9ac30

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gdb vmlinux vmcore

set print repeats 100
set print elements 0
set logging file XXX
set pagination off
set logging on
p {char*} 0xffffffff81a9ac30
quit

---

vi XXX
:%s/\\n/\r/g
方法二、

是另一命令,但不好用

1
2
3
4
5
6
7
8
9
10
11
cat /proc/kallsyms | grep log_end
ffffffff81e30de0 b log_end

x/1dw 0xffffffff81e30de0
0xffffffff81e30de0:     85689

x/1xg 0xffffffff81a9ac30
0xffffffff81a9ac30:     0xffffffff81e30ee0

显示最后4000字符
x/5s 0xffffffff81e30ee0+85689-4000

gdb 没有debug信息step单步调试

1
step <count>

单步跟踪,如果有函数调用,他会进入该函数。进入函数的前提是,此函数被编译有 debug信息。很像 VC等工具中的 step in。后面可以加 count也可以不加,不加表示一条条地执行,加表示执行后面的 count条指令,然后再停住。

1
next <count>

同样单步跟踪,如果有函数调用,他不会进入该函数。很像 VC等工具中的 step over。后面可以加 count也可以不加,不加表示一条条地执行,加表示执行后面的 count条指令,然后再停住。

1
2
3
4
5
6
set step-mode [on/off]
set step-mode on
打开 step-mode模式,于是,在进行单步跟踪时,程序不会因为没有 debug信息而不停住。这个参数有很利于查看机器码。

set step-mod off
关闭 step-mode模式。

gdb x命令语法

可以使用examine命令(简写是x)来查看内存地址中的值。x命 令的语 法如下所示:

1
x/<n/f/u> <addr>  

n、f、u是可选的参数。
n是一个正整数,表示需要显示的内存单元的个数, 也就是说从当前地址向后显示几个 内存单元的内容,一个内存单元的大小由后面的u定义。
f 表示显示的格式,参见下面。如果地址所指的是字符串,那么格式可以是s,如果地十是指令地址,那么格式可以是i。
u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字 节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
表示一个内存地址。

注意:严格区分n 和u的关系,n表示单元个数,u表示每个单元的大小。

n/f/u三个参数可以一起使用。例如:
命令:x/3uh 0x54320 表示,从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示输出三个单位,u表示按十六进制显示。

输出格式

一般来说,GDB会根据变量的类型输出变量的值。但你也可以自定义GDB的输出的格式。例如,你想输出一个整数的十六进制,或是二进制来查看这个整型变量 的中的位的情况。要做到这样,你可以使用GDB的数据显示格式:

1
2
3
4
5
6
7
8
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。

(gdb) help x Examine memory: x/FMT ADDRESS. ADDRESS is an expression for the memory address to examine. FMT is a repeat count followed by a format letter and a size letter. Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char) and s(string). Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes). The specified number of objects of the specified size are printed according to the format.

Defaults for format and size letters are those previously used. Default count is 1. Default address is following last thing printed with this command or “print”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(gdb) p f1
$4 = 8.25
(gdb) p f2
$5 = 125.5
(gdb) x/x &f1
0xbffff380:   0x41040000
(gdb) x/xw &f1
0xbffff380:   0x41040000
(gdb) x/xw &f2
0xbffff384:   0x42fb0000
(gdb) x/2xw &f1
0xbffff380:   0x41040000   0x42fb0000
(gdb) x/4xw &f1
0xbffff380:   0x41040000   0x42fb0000   0xbffff408   0x00bcba66
(gdb) x/tw &f1
0xbffff380:   01000001000001000000000000000000
(gdb) x/2tw &f1
0xbffff380:   01000001000001000000000000000000   01000010111110110000000000000000
(gdb) p record
$10 = {12, 76, 48, 62, 94, 17, 32, 37, 52, 69}
(gdb) p &record
$11 = (int (*)[10]) 0x8049740
(gdb) x/4uw 0x8049740
0x8049740 <record>:   12   76   48   62
(gdb) x/6ow 0x8049740
0x8049740 <record>:   014   0114   060   076
0x8049750 <record+16>:   0136   021

gdb print 语法

1
2
print <expr>
print /<f> <expr>

<expr>是表达式,是你所调试的程序的语言的表达式(GDB可以调试多种编程语言),是输出的格式,比如,假如要把表达式按16进制的格式输出,那么就是/x。

一、表达式

print和许多GDB的命令一样,可以接受一个表达式,GDB会根据当前的程序运行的数据来计算这个表达式,既然是表达式,那么就可以是当前程序运行中的const常量、变量、函数等内容。可惜的是GDB不能使用你在程序中所定义的宏。

表达式的语法应该是当前所调试的语言的语法,由于C/C++是一种大众型的语言,所以,本文中的例子都是关于C/C++的。(而关于用GDB调试其它语言的章节,我将在后面介绍)

在表达式中,有几种GDB所支持的操作符,它们可以用在任何一种语言中。
@是一个和数组有关的操作符,在后面会有更具体的说明。
::指定一个在文件或是一个函数中的变量。
{<type>} <addr>表示一个指向内存地址的类型为type的一个对象。 p {char*} 0xffffffff12345678

二、程序变量

在GDB中,你可以随时查看以下三种变量的值:
1、全局变量(所有文件可见的)
2、静态全局变量(当前文件可见的)
3、局部变量(当前Scope可见的)
假如你的局部变量和全局变量发生冲突(也就是重名),一般情况下是局部变量会隐藏全局变量,也就是说,假如一个全局变量和一个函数中的局部变量同名时,假 如当前停止点在函数中,用print显示出的变量的值会是函数中的局部变量的值。假如此时你想查看全局变量的值时,你可以使用“::”操作符:

1
2
file::variable
function::variable

可以通过这种形式指定你所想查看的变量,是哪个文件中的或是哪个函数中的。例如,查看文件f2.c中的全局变量x的值:

1
gdb) p 'f2.c'::x

当然,“::”操作符会和C++中的发生冲突,GDB能自动识别“::” 是否C++的操作符,所以你不必担心在调试C++程序时会出现异常。

另外,需要注重的是,假如你的程序编译时开启了优化选项,那么在用GDB调试被优化过的程序时,可能会发生某些变量不能访问,或是取值错误码的情况。这个是很正常的,因为优化程序会删改你的程序,整理你程序的语句顺序,剔除一些无意义的变量等,所以在GDB调 试这种程序时,运行时的指令和你所编写指令就有不一样,也就会出现你所想象不到的结果。对付这种情况时,需要在编译程序时关闭编译优化。一般来说,几乎所 有的编译器都支持编译优化的开关,例如,GNU的C/C++编译器GCC,你可以使用“-gstabs”选项来解决这个问题。关于编译器的参数,还请查看 编译器的使用说明文档。

三、数组

有时候,你需要查看一段连续的内存空间的值。比如数组的一段,或是动态分配的数据的大小。你可以使用GDB的“@”操作符,“@”的左边是第一个内存的地址的值,“@”的右边则你你想查看内存的长度。例如,你的程序中有这样的语句:

1
int *array = (int *) malloc (len * sizeof (int));

于是,在GDB调试过程中,你可以以如下命令显示出这个动态数组的取值:

1
p *array@len

@的左边是数组的首地址的值,也就是变量array所指向的内容,右边则是数据的长度,其保存在变量len中,其输出结果,大约是下面这个样子的:

1
2
(gdb) p *array@len
$1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40}

假如是静态数组的话,可以直接用print数组名,就可以显示数组中所有数据的内容了。