gdb+gdbserver
内容摘要 远程调试环境由宿主机GDB和目标机调试stub共同构成,两者通过串口或TCP连接。使用 GDB标准程串行协议协同工作,实现对目标机上的系统内核和上层应用的监控和调试功能。调试stub是嵌入式系统中的一段代码,作为宿主机GDB和目标机调试程序间的一个媒介而存在。 就目前而言,嵌入式Linux系统中,主要有三种远程调试方法,分别适用于不同场合的调试工作:用ROM Monitor调试目标机程序、用KGDB调试系统内核和用gdbserver调试用户空间程序。这三种调试方法的区别主要在于,目标机远程调试stub 的存在形式的不同,而其设计思路和实现方法则是大致相同的。 而我们最常用的是调试应用程序。就是采用gdb+gdbserver的方式进行调试。在很多情况下,用户需要对一个应用程序进行反复调试,特别是复杂的程序。采用GDB方法调试,由于嵌入式系统资源有限性,一般不能直接在目标系统上进行调试,通常采用gdb+gdbserver的方式进行调试。
gdb的简单使用
GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在 UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。一般来说,GDB主要帮忙你完成下面四个方面的功能: 1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
3、当程序被停住时,可以检查此时你的程序中所发生的事。
4、动态的改变你程序的执行环境。从上面看来,GDB和一般的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上,你会发现GDB这个调试工具的强大,大家可能比较习惯了图形化的调试工具,但有时候,命令行的调试工具却有着图形化工具所不能完成的功能。让我们一一看来。
一个调试示例
—————— 源程序:tst.c 1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
21 }
22
23 printf("result[1-100] = %d \\n", result );
24 printf("result[1-250] = %d \\n", func(250) );
25 } 编译生成执行文件:(Linux下)
hchen/test> cc -g tst.c -o tst 使用GDB调试: hchen/test> gdb tst <---------- 启动GDB
GNU gdb 5.1.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 "i386-suse-linux"...
(gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。
1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
(gdb) <-------------------- 直接回车表示,重复上一次命令
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
(gdb) break 16 <-------------------- 设置断点,在源程序第16行处。
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func <-------------------- 设置断点,在函数func()入口处。
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break <-------------------- 查看断点信息。
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048496 in main at tst.c:16
2 breakpoint keep y 0x08048456 in func at tst.c:5
(gdb) r <--------------------- 运行程序,run命令简写
Starting program: /home/hchen/test/tst Breakpoint 1, main () at tst.c:17 <---------- 在断点处停住。
17 long result = 0;
(gdb) n <--------------------- 单条语句执行,next命令简写。
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) n
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) c <--------------------- 继续运行程序,continue命令简写。
Continuing.
result[1-100] = 5050 <----------程序输出。 Breakpoint 2, func (n=250) at tst.c:5
5 int sum=0,i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p i <--------------------- 打印变量i的值,print命令简写。
$1 = 134513808
(gdb) n
8 sum+=i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8 sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt <--------------------- 查看函数堆栈。
#0 func (n=250) at tst.c:5
#1 0x080484e4 in main () at tst.c:24
#2 0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish <--------------------- 退出函数。
Run till exit from #0 func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24 printf("result[1-250] = %d \n", func(250) );
Value returned is $6 = 31375
(gdb) c <--------------------- 继续运行。
Continuing.
result[1-250] = 31375 <----------程序输出。 Program exited with code 027. <--------程序退出,调试结束。
(gdb) q <--------------------- 退出gdb。
hchen/test>
gdb+gdbserver方式进行ARM程序调试
【摘要】:本文首先介绍了gdb+gdbserver相关的概念,然后介绍了其下载、编译、安装等过程;接着介绍了利用gdb+gdbserver调试应用程序的流程及实例等;最后分析了下gdb+gdbserver安装过程中的常见问题。
【关键词】:gdb,gdbserver,远程调试
目录
一、gdb+gdbserver总体介绍... 1
二、源代码下载... 1
三、配置编译及安装下载... 1
四、gdb+gdbserver nfs调试流程... 2
五、如何利用串口调试... 3
六、实战调试... 3
七、linux下安装gdbserver问题... 5
一、gdb+gdbserver总体介绍
远程调试环境由宿主机GDB和目标机调试stub共同构成,两者通过串口或TCP连接。使用 GDB标准程串行协议协同工作,实现对目标机上的系统内核和上层应用的监控和调试功能。调试stub是嵌入式系统中的一段代码,作为宿主机GDB和目标机调试程序间的一个媒介而存在。
就目前而言,嵌入式Linux系统中,主要有三种远程调试方法,分别适用于不同场合的调试工作:用ROM Monitor调试目标机程序、用KGDB调试系统内核和用gdbserver调试用户空间程序。这三种调试方法的区别主要在于,目标机远程调试stub 的存在形式的不同,而其设计思路和实现方法则是大致相同的。
而我们最常用的是调试应用程序。就是采用gdb+gdbserver的方式进行调试。在很多情况下,用户需要对一个应用程序进行反复调试,特别是复杂的程序。采用GDB方法调试,由于嵌入式系统资源有限性,一般不能直接在目标系统上进行调试,通常采用gdb+gdbserver的方式进行调试。
二、源代码下载
嵌入式Linux的GDB调试环境由Host和Target两部分组成,Host端使用arm-linux-gdb,Target Board端使用gdbserver。这样,应用程序在嵌入式目标系统上运行,而gdb调试在Host端,所以要采用远程调试(remote)的方法。进行GDB调试,目标系统必须包括gdbserver程序(在主机上正对硬件平台编译成功后下载到目标机上),宿主机也必须安装GDB 程序。一般Linux发行版中都有一个可以运行的GDB,但开发人员不能直接使用该发行版中的GDB来做远程调试,而要获取GDB的源代码包,针对arm 平台作一个简单配置,重新编译得到相应GDB。GDB的源代码包可以从
http://www.gnu.org/software/gdb/download/
http://ftp.gnu.org/gnu/gdb/ 211.95.105.202:3128可以上去的,所有的版本都有啊
http: //ftp.cs.pu.edu.tw/linux/sourceware/gdb/releases/下载
ftp://ftp.gnu.org/gnu/gdb
外网的ftp我经常上不去,国内常见的开源社区的下载频道通常都有下载的http://download.chinaunix.net/download/0004000/3482.shtml,最新版本为gdb-6.5.tar.bz2。下载到某个目录,笔者下载到/opt/。但要注意,gdb的版本需要和croostool 相匹配。
三、配置编译及安装下载
下载完后,进入/opt/目录,配置编译步骤如下:
#tar jxvf gdb-6.5-tar-bz2 #cd gdb-6.5 #./configure --target=arm-linux --prefix=/usr/local/arm-gdb –v (--target配置gdb的目标平台,--prefix配置安装路径,当然其他路径也可以, .跟下面配置一致即可,须在环境变量中声明,启动arm-linux-gdb需要,可更改/etc/profile或~/.bash_profile或~/.bashrc,添加export PATH=$PATH:/usr/local/arm-gdb/bin,这样可以找到路径) #make |
#make install (生成arm-linux-gdb,并存入/usr/local/arm-gdb /bin/,查询确认下) 也可以启动arm-linux-gdb,若成功,则证明安装无误 进入gdb/gdbserver目录: [root@dding gdbserver]# pwd /opt/gdb-6.5/gdb/gdbserver [root@dding gdbserver]# 必须在gdbserver目录下运行配置命令,此时才能用相对路径 #./configure --target=arm-linux --host=arm-linux (--target=arm-linux表示目标平台,--host表示主机端运行的是arm-linux-gdb,不需要配置—prefix,因为gdbserver不在主机端安装运行) #make CC=/usr/local/arm/2.95.3/bin/arm-linux-gcc (这一步要指定你自己的arm-linux-gcc的绝对位置,我试过相对的不行,提示make: arm-linux-gcc: Command not found,可好多人都用的相对路径,即直接赋值arm-linux-gcc,可采取make时传递参数,也可以直接修改gdbserver目录下的Makefile文件中的环境变量CC) |
没有错误的话就在gdbserver目录下生成gdbserver可执行文件,注意此时要更改其属性,否则可能会出现无法访问的情况,chmod 777 gdbserver将其更改为任何人都可以读写执行;使用arm-linux-strip命令处理一下gdbserver,将多余的符号信息删除,可让elf文件更精简,通常在应用程序的最后发布时使用;然后把它烧写到flash的根文件系统分区的/usr/bin(在此目录下,系统可以自动找到应用程序,否则必须到gdbserver所在目录下运行之),或通过nfs mount的方式都可以。只要保证gdbserver能在开发板上运行就行。
四、gdb+gdbserver nfs调试流程
下面就可以用gdb+gdbserver调试我们开发板上的程序了。在目标板上运行 gdbserver,其实就是在宿主机的minicom下。我是在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端口(你也可以设其他可用的值,当然必须跟主机的gdb一致)开启了一个调试进程,hello为要调试的程序(必须-g加入调试信息)。
出现提示:
Process /tmp/hello created: pid=80 Listening on port 2345 (另一个终端下) #cd / #export PATH=$PATH:/usr/local/arm-gdb/bin #arm-linux-gdb hello 最后一行显示:This GDB was configured as “--host=i686-pc-linux-gnu,--target=arm-linux”...,如果不一致说明arm-linux-gdb有问题 说明此gdb在X86的Host上运行,但是调试目标是ARM代码。 (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) |
注意:你的端口号必须与gdbserver开启的端口号一致,这样才能进行通信。建立链接后,就可以进行调试了。调试在Host端,跟gdb调试方法相同。注意的是要用“c”来执行命令,不能用“r”。因为程序已经在Target Board上面由gdbserver启动了。结果输出是在Target Board端,用超级终端查看。连接成功,这时候就可以输入各种GDB命令如list、run、next、step、break等进行程序调试了。
以上针对通过nfs mount和tftp的方式,只能在主机上调试好后下载到开发板上运行,如果有错误要反复这个过程,繁琐不说,有些程序只能在开发板上调试。所以笔者采用了gdbserver的远程调试方式。希望对大家调试程序有用!
五、如何利用串口调试
如果你用串口1调试hello的话,你就要现在板子上运行命令:
gdbserver hello /dev/ttyS0 (详情可以参考gdbserver目录下的readme文件)
这时gdbserver就在等待gdb的应答信号了。
然后在pc机上运行命令:
xxx-linux-gdb hello
在xxx-linux-gdb里敲入入下命令:
set remotedevice /dev/ttyS0(这里设置串口1)
set remote baud 9600 (这里设置串口波特率)
set debug remote 1(可选)
target remote /dev/ttyS0
操作到这儿,gdb就应该和gdbserver联系上了。
六、实战调试
1.编辑文件
# vi gdbtest.c
1 #include <stdio.h>
2
3 int
4 func(int n){
5 int sum=0, i;
6 for (i=0; i<n; i++){
7 sum += i;
8 }
9 return sum;
10 }
11
12 int
13 main(void)
14 {
15 int i;
16 long result = 0;
17 for (i=0; i<=100; i++){
18 result += i;
19 }
20
21 printf("result[1-100] = %d \n", result);
22 printf("resutl[1-225] = %d \n", func(255));
23
24 return 0;
25 }
# arm-linux-gcc -g gdbtest.c -o gdbtest // 交叉编译
2.下载文件到目标板: gdbtest和gdbserver
假设 host pc ip:192.168.1.45
board ip:192.168.1.180
将文件拷贝到目标板上:
先将gdbtest和gdbserver两个文件拷贝到主机的/tftpboot目录下,此时系统主机和目标机都必须能够支持nfs
在目标板的Linux中运行:
#mount 192.168.1.108:/tftpboot /mnt/nfs
#cd /mnt/nfs
#ls
看是否有gdbtest和gdbserver两个文件。
3.运行调试
client board:
#./gdbserver 192.168.1.45:1234 gdbtest // 目标板上运行gdbtest 监听端口1234
[root@AT91RM9200DK arm]$./gdbserver 192.168.0.12:2345 mainparacarm
./gdbserver: error in loading shared libraries: libthread_db.so.1: cannot open [root@AT91RM9200DK arm]$
host pc:
#cd /usr/local/arm-gdb/bin/ 以便能够运行arm-linux-gdb,但是无此必要,可在环境变量中设置此路径即可。
#copy gdbtest /usr/local/arm-gdb/bin/ // 将前面编译的文件gdbtest拷贝到此目录
#./arm-linux-gdb gdbtest
(gdb)target remote 192.168.1.180:1234 // 连接到开发板成功后就可以
进行调试
(gdb)list or l
(gdb)break func
(gdb)break 22
(gdb)info br
(gdb)continue or c // 这里不能用 run
(gdb)next or n
(gdb)print or p result
(gdb) finish // 跳出func函数
(gdb) next
(gdb) quit
建立连接后进行gdb远程调试和gdb本地调试方法相同
七、 linux下安装gdbserver问题
toolchain version: gdb的版本可能和交叉编译器有很大的关系
gcc-3.3.2
glibc-2.2.5
binutils-2.15 此为croostool 3.3.2
安装步骤:
下载解压gdb-6.6
#cd gdb-6.6
#./configure --target=arm-linux --prefix=/usr/local/arm-gdb –v
#make & make install
OK,然后:
#export PATH=$PATH:/usr/local/arm-gdb
进入gdbserver目录:
#./configure --target=arm-linux --host=arm-linux
#make CC=/usr/local/armv5l/3.3.2/bin/armv5l-linux-gcc
出错:
/usr/local/armv5l/3.3.2/bin/armv5l-linux-gcc -c -Wall -g -O2 -I. -I. -I./../regformats -I./../../include -I../../bfd -I./../../bfd linux-arm-low.c
linux-arm-low.c:35:21: sys/reg.h: 没有那个文件或目录
make: *** [linux-arm-low.o] 错误 1
然后把/usr/include/sys/reg.h copy到/usr/local/armv5l-2.6.x/3.3.2/armv5l-linux/include/sys/reg.h,即将该文件拷贝到交叉编译器的include目录下,再make,显示错误:
/usr/local/armv5l/3.3.2/bin/armv5l-linux-gcc -c -Wall -g -O2 -I. -I. -I./../regformats -I./../../include -I../../bfd -I./../../bfd thread-db.c
thread-db.c: In function `thread_db_err_str':
thread-db.c:95: error: `TD_VERSION' undeclared (first use in this function)
thread-db.c:95: error: (Each undeclared identifier is reported only once
thread-db.c:95: error: for each function it appears in.)
thread-db.c: In function `thread_db_get_tls_address':
thread-db.c:336: warning: implicit declaration of function `td_thr_tls_get_addr'
thread-db.c:336: warning: cast to pointer from integer of different size
thread-db.c:340: warning: cast from pointer to integer of different size
make: *** [thread-db.o] 错误 1
本想继续fix error,但是感觉不太对,请问各位,是什么原因呢?
是不是CC的target写错了?应该是arm-linux还是armv5l-linux?
1.
make: *** [linux-arm-low.o] Error 1
[root@dding gdbserver]#
[root@dding gdbserver]# gedit config.h
/* Define to 1 if you have the <sys/reg.h> header file. */
/*define HAVE_SYS_REG_H 1 */
/*have no <sys/reg.h> header file. so undefine 20070402 dding */
2.
thread-db.c: In function `thread_db_err_str': gdb6.5
thread-db.c:95: `TD_VERSION' undeclared (first use in this function)
[root@dding gdbserver]# gedit config.h
94 #ifdef HAVE_TD_VERSION
95 case TD_VERSION:
96 return "version mismatch between libthread_db and libpthread";
97 #endif
/* Define if TD_VERSION is available. */
/*#define HAVE_TD_VERSION 1 */
/*have no TD_VERSION. so undefine 20070402 dding */
gdb6.1 没有此问题
3.
[root@AT91RM9200DK arm]$./gdbserver 192.168.0.12:2345 mainparacarm gdb6.5
./gdbserver: error in loading shared libraries: libthread_db.so.1: cannot open
[root@AT91RM9200DK arm]$./gdbserver 192.168.0.14:2345 mainparacarm gdb6.1
./gdbserver: error in loading shared libraries: libthread_db.so.1: cannot open shared object file: No such file or directory
我已经加了libthread_db.so.1共享库为什么还打不开呢????共享库和cpu类型有关吗?
gdbserver: error while loading shared libraries: libthread_db.so.1: cannot open
shared object file: No such file or director
****编译GDB的时候搞成静态的就好了.我想编译选项里应该有. 要不你就在Makefile里加上CFLAGS += -static
LDFLAGS += -static
这两个的其中一个应该就可以了,不过还是两个都加上吧.
***/lib there is no libthread_db.so.1 Can i use nfs to copy libthread_db.so.1 to /lib? But now i cannot find this file, and is there any for cross 3.3.2?
libpthread-0.8.so
libpthread.so libpthread.so.0 libresolv-2.1.3.so
libresolv.so.2 libstdc++.a.2.10.0 libtermcap.so.2
[root@AT91RM9200DK arm]$cp libthread_db-1.0.so libthread_db.so.1
[root@AT91RM9200DK arm]$cp libthread_db.so.1 /lib/
[root@AT91RM9200DK arm]$./gdbserver 192.168.0.12:2345 mainparacarm
./gdbserver: /lib/libc.so.6: version `GLIBC_2.2' not found (required by /lib/li)
难道目前的gdb 6.5 版本太高,需要内核版本和交叉编译器与之匹配?实在不行,就试试低版本的gdb
参考文档
http://blog.chinaunix.net/u/27802/showart_211833.html
http://litttlebylittle.bokee.com/5803108.html
http://www.blogcn.com/u/93/99/litcatfish/index.html
内容摘要 远程调试环境由宿主机GDB和目标机调试stub共同构成,两者通过串口或TCP连接。使用 GDB标准程串行协议协同工作,实现对目标机上的系统内核和上层应用的监控和调试功能。调试stub是嵌入式系统中的一段代码,作为宿主机GDB和目标机调试程序间的一个媒介而存在。 就目前而言,嵌入式Linux系统中,主要有三种远程调试方法,分别适用于不同场合的调试工作:用ROM Monitor调试目标机程序、用KGDB调试系统内核和用gdbserver调试用户空间程序。这三种调试方法的区别主要在于,目标机远程调试stub 的存在形式的不同,而其设计思路和实现方法则是大致相同的。 而我们最常用的是调试应用程序。就是采用gdb+gdbserver的方式进行调试。在很多情况下,用户需要对一个应用程序进行反复调试,特别是复杂的程序。采用GDB方法调试,由于嵌入式系统资源有限性,一般不能直接在目标系统上进行调试,通常采用gdb+gdbserver的方式进行调试。
gdb的简单使用
GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在 UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。一般来说,GDB主要帮忙你完成下面四个方面的功能: 1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
3、当程序被停住时,可以检查此时你的程序中所发生的事。
4、动态的改变你程序的执行环境。从上面看来,GDB和一般的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上,你会发现GDB这个调试工具的强大,大家可能比较习惯了图形化的调试工具,但有时候,命令行的调试工具却有着图形化工具所不能完成的功能。让我们一一看来。
一个调试示例
—————— 源程序:tst.c 1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
21 }
22
23 printf("result[1-100] = %d \\n", result );
24 printf("result[1-250] = %d \\n", func(250) );
25 } 编译生成执行文件:(Linux下)
hchen/test> cc -g tst.c -o tst 使用GDB调试: hchen/test> gdb tst <---------- 启动GDB
GNU gdb 5.1.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 "i386-suse-linux"...
(gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。
1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
(gdb) <-------------------- 直接回车表示,重复上一次命令
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
(gdb) break 16 <-------------------- 设置断点,在源程序第16行处。
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func <-------------------- 设置断点,在函数func()入口处。
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break <-------------------- 查看断点信息。
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048496 in main at tst.c:16
2 breakpoint keep y 0x08048456 in func at tst.c:5
(gdb) r <--------------------- 运行程序,run命令简写
Starting program: /home/hchen/test/tst Breakpoint 1, main () at tst.c:17 <---------- 在断点处停住。
17 long result = 0;
(gdb) n <--------------------- 单条语句执行,next命令简写。
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) n
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) c <--------------------- 继续运行程序,continue命令简写。
Continuing.
result[1-100] = 5050 <----------程序输出。 Breakpoint 2, func (n=250) at tst.c:5
5 int sum=0,i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p i <--------------------- 打印变量i的值,print命令简写。
$1 = 134513808
(gdb) n
8 sum+=i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8 sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt <--------------------- 查看函数堆栈。
#0 func (n=250) at tst.c:5
#1 0x080484e4 in main () at tst.c:24
#2 0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish <--------------------- 退出函数。
Run till exit from #0 func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24 printf("result[1-250] = %d \n", func(250) );
Value returned is $6 = 31375
(gdb) c <--------------------- 继续运行。
Continuing.
result[1-250] = 31375 <----------程序输出。 Program exited with code 027. <--------程序退出,调试结束。
(gdb) q <--------------------- 退出gdb。
hchen/test>
gdb+gdbserver方式进行ARM程序调试
【摘要】:本文首先介绍了gdb+gdbserver相关的概念,然后介绍了其下载、编译、安装等过程;接着介绍了利用gdb+gdbserver调试应用程序的流程及实例等;最后分析了下gdb+gdbserver安装过程中的常见问题。
【关键词】:gdb,gdbserver,远程调试
目录
一、gdb+gdbserver总体介绍... 1
二、源代码下载... 1
三、配置编译及安装下载... 1
四、gdb+gdbserver nfs调试流程... 2
五、如何利用串口调试... 3
六、实战调试... 3
七、linux下安装gdbserver问题... 5
一、gdb+gdbserver总体介绍
远程调试环境由宿主机GDB和目标机调试stub共同构成,两者通过串口或TCP连接。使用 GDB标准程串行协议协同工作,实现对目标机上的系统内核和上层应用的监控和调试功能。调试stub是嵌入式系统中的一段代码,作为宿主机GDB和目标机调试程序间的一个媒介而存在。
就目前而言,嵌入式Linux系统中,主要有三种远程调试方法,分别适用于不同场合的调试工作:用ROM Monitor调试目标机程序、用KGDB调试系统内核和用gdbserver调试用户空间程序。这三种调试方法的区别主要在于,目标机远程调试stub 的存在形式的不同,而其设计思路和实现方法则是大致相同的。
而我们最常用的是调试应用程序。就是采用gdb+gdbserver的方式进行调试。在很多情况下,用户需要对一个应用程序进行反复调试,特别是复杂的程序。采用GDB方法调试,由于嵌入式系统资源有限性,一般不能直接在目标系统上进行调试,通常采用gdb+gdbserver的方式进行调试。
二、源代码下载
嵌入式Linux的GDB调试环境由Host和Target两部分组成,Host端使用arm-linux-gdb,Target Board端使用gdbserver。这样,应用程序在嵌入式目标系统上运行,而gdb调试在Host端,所以要采用远程调试(remote)的方法。进行GDB调试,目标系统必须包括gdbserver程序(在主机上正对硬件平台编译成功后下载到目标机上),宿主机也必须安装GDB 程序。一般Linux发行版中都有一个可以运行的GDB,但开发人员不能直接使用该发行版中的GDB来做远程调试,而要获取GDB的源代码包,针对arm 平台作一个简单配置,重新编译得到相应GDB。GDB的源代码包可以从
http://www.gnu.org/software/gdb/download/
http://ftp.gnu.org/gnu/gdb/ 211.95.105.202:3128可以上去的,所有的版本都有啊
http: //ftp.cs.pu.edu.tw/linux/sourceware/gdb/releases/下载
ftp://ftp.gnu.org/gnu/gdb
外网的ftp我经常上不去,国内常见的开源社区的下载频道通常都有下载的http://download.chinaunix.net/download/0004000/3482.shtml,最新版本为gdb-6.5.tar.bz2。下载到某个目录,笔者下载到/opt/。但要注意,gdb的版本需要和croostool 相匹配。
三、配置编译及安装下载
下载完后,进入/opt/目录,配置编译步骤如下:
#tar jxvf gdb-6.5-tar-bz2 #cd gdb-6.5 #./configure --target=arm-linux --prefix=/usr/local/arm-gdb –v (--target配置gdb的目标平台,--prefix配置安装路径,当然其他路径也可以, .跟下面配置一致即可,须在环境变量中声明,启动arm-linux-gdb需要,可更改/etc/profile或~/.bash_profile或~/.bashrc,添加export PATH=$PATH:/usr/local/arm-gdb/bin,这样可以找到路径) #make |
#make install (生成arm-linux-gdb,并存入/usr/local/arm-gdb /bin/,查询确认下) 也可以启动arm-linux-gdb,若成功,则证明安装无误 进入gdb/gdbserver目录: [root@dding gdbserver]# pwd /opt/gdb-6.5/gdb/gdbserver [root@dding gdbserver]# 必须在gdbserver目录下运行配置命令,此时才能用相对路径 #./configure --target=arm-linux --host=arm-linux (--target=arm-linux表示目标平台,--host表示主机端运行的是arm-linux-gdb,不需要配置—prefix,因为gdbserver不在主机端安装运行) #make CC=/usr/local/arm/2.95.3/bin/arm-linux-gcc (这一步要指定你自己的arm-linux-gcc的绝对位置,我试过相对的不行,提示make: arm-linux-gcc: Command not found,可好多人都用的相对路径,即直接赋值arm-linux-gcc,可采取make时传递参数,也可以直接修改gdbserver目录下的Makefile文件中的环境变量CC) |
没有错误的话就在gdbserver目录下生成gdbserver可执行文件,注意此时要更改其属性,否则可能会出现无法访问的情况,chmod 777 gdbserver将其更改为任何人都可以读写执行;使用arm-linux-strip命令处理一下gdbserver,将多余的符号信息删除,可让elf文件更精简,通常在应用程序的最后发布时使用;然后把它烧写到flash的根文件系统分区的/usr/bin(在此目录下,系统可以自动找到应用程序,否则必须到gdbserver所在目录下运行之),或通过nfs mount的方式都可以。只要保证gdbserver能在开发板上运行就行。
四、gdb+gdbserver nfs调试流程
下面就可以用gdb+gdbserver调试我们开发板上的程序了。在目标板上运行 gdbserver,其实就是在宿主机的minicom下。我是在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端口(你也可以设其他可用的值,当然必须跟主机的gdb一致)开启了一个调试进程,hello为要调试的程序(必须-g加入调试信息)。
出现提示:
Process /tmp/hello created: pid=80 Listening on port 2345 (另一个终端下) #cd / #export PATH=$PATH:/usr/local/arm-gdb/bin #arm-linux-gdb hello 最后一行显示:This GDB was configured as “--host=i686-pc-linux-gnu,--target=arm-linux”...,如果不一致说明arm-linux-gdb有问题 说明此gdb在X86的Host上运行,但是调试目标是ARM代码。 (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) |
注意:你的端口号必须与gdbserver开启的端口号一致,这样才能进行通信。建立链接后,就可以进行调试了。调试在Host端,跟gdb调试方法相同。注意的是要用“c”来执行命令,不能用“r”。因为程序已经在Target Board上面由gdbserver启动了。结果输出是在Target Board端,用超级终端查看。连接成功,这时候就可以输入各种GDB命令如list、run、next、step、break等进行程序调试了。
以上针对通过nfs mount和tftp的方式,只能在主机上调试好后下载到开发板上运行,如果有错误要反复这个过程,繁琐不说,有些程序只能在开发板上调试。所以笔者采用了gdbserver的远程调试方式。希望对大家调试程序有用!
五、如何利用串口调试
如果你用串口1调试hello的话,你就要现在板子上运行命令:
gdbserver hello /dev/ttyS0 (详情可以参考gdbserver目录下的readme文件)
这时gdbserver就在等待gdb的应答信号了。
然后在pc机上运行命令:
xxx-linux-gdb hello
在xxx-linux-gdb里敲入入下命令:
set remotedevice /dev/ttyS0(这里设置串口1)
set remote baud 9600 (这里设置串口波特率)
set debug remote 1(可选)
target remote /dev/ttyS0
操作到这儿,gdb就应该和gdbserver联系上了。
六、实战调试
1.编辑文件
# vi gdbtest.c
1 #include <stdio.h>
2
3 int
4 func(int n){
5 int sum=0, i;
6 for (i=0; i<n; i++){
7 sum += i;
8 }
9 return sum;
10 }
11
12 int
13 main(void)
14 {
15 int i;
16 long result = 0;
17 for (i=0; i<=100; i++){
18 result += i;
19 }
20
21 printf("result[1-100] = %d \n", result);
22 printf("resutl[1-225] = %d \n", func(255));
23
24 return 0;
25 }
# arm-linux-gcc -g gdbtest.c -o gdbtest // 交叉编译
2.下载文件到目标板: gdbtest和gdbserver
假设 host pc ip:192.168.1.45
board ip:192.168.1.180
将文件拷贝到目标板上:
先将gdbtest和gdbserver两个文件拷贝到主机的/tftpboot目录下,此时系统主机和目标机都必须能够支持nfs
在目标板的Linux中运行:
#mount 192.168.1.108:/tftpboot /mnt/nfs
#cd /mnt/nfs
#ls
看是否有gdbtest和gdbserver两个文件。
3.运行调试
client board:
#./gdbserver 192.168.1.45:1234 gdbtest // 目标板上运行gdbtest 监听端口1234
[root@AT91RM9200DK arm]$./gdbserver 192.168.0.12:2345 mainparacarm
./gdbserver: error in loading shared libraries: libthread_db.so.1: cannot open [root@AT91RM9200DK arm]$
host pc:
#cd /usr/local/arm-gdb/bin/ 以便能够运行arm-linux-gdb,但是无此必要,可在环境变量中设置此路径即可。
#copy gdbtest /usr/local/arm-gdb/bin/ // 将前面编译的文件gdbtest拷贝到此目录
#./arm-linux-gdb gdbtest
(gdb)target remote 192.168.1.180:1234 // 连接到开发板成功后就可以
进行调试
(gdb)list or l
(gdb)break func
(gdb)break 22
(gdb)info br
(gdb)continue or c // 这里不能用 run
(gdb)next or n
(gdb)print or p result
(gdb) finish // 跳出func函数
(gdb) next
(gdb) quit
建立连接后进行gdb远程调试和gdb本地调试方法相同
七、 linux下安装gdbserver问题
toolchain version: gdb的版本可能和交叉编译器有很大的关系
gcc-3.3.2
glibc-2.2.5
binutils-2.15 此为croostool 3.3.2
安装步骤:
下载解压gdb-6.6
#cd gdb-6.6
#./configure --target=arm-linux --prefix=/usr/local/arm-gdb –v
#make & make install
OK,然后:
#export PATH=$PATH:/usr/local/arm-gdb
进入gdbserver目录:
#./configure --target=arm-linux --host=arm-linux
#make CC=/usr/local/armv5l/3.3.2/bin/armv5l-linux-gcc
出错:
/usr/local/armv5l/3.3.2/bin/armv5l-linux-gcc -c -Wall -g -O2 -I. -I. -I./../regformats -I./../../include -I../../bfd -I./../../bfd linux-arm-low.c
linux-arm-low.c:35:21: sys/reg.h: 没有那个文件或目录
make: *** [linux-arm-low.o] 错误 1
然后把/usr/include/sys/reg.h copy到/usr/local/armv5l-2.6.x/3.3.2/armv5l-linux/include/sys/reg.h,即将该文件拷贝到交叉编译器的include目录下,再make,显示错误:
/usr/local/armv5l/3.3.2/bin/armv5l-linux-gcc -c -Wall -g -O2 -I. -I. -I./../regformats -I./../../include -I../../bfd -I./../../bfd thread-db.c
thread-db.c: In function `thread_db_err_str':
thread-db.c:95: error: `TD_VERSION' undeclared (first use in this function)
thread-db.c:95: error: (Each undeclared identifier is reported only once
thread-db.c:95: error: for each function it appears in.)
thread-db.c: In function `thread_db_get_tls_address':
thread-db.c:336: warning: implicit declaration of function `td_thr_tls_get_addr'
thread-db.c:336: warning: cast to pointer from integer of different size
thread-db.c:340: warning: cast from pointer to integer of different size
make: *** [thread-db.o] 错误 1
本想继续fix error,但是感觉不太对,请问各位,是什么原因呢?
是不是CC的target写错了?应该是arm-linux还是armv5l-linux?
1.
make: *** [linux-arm-low.o] Error 1
[root@dding gdbserver]#
[root@dding gdbserver]# gedit config.h
/* Define to 1 if you have the <sys/reg.h> header file. */
/*define HAVE_SYS_REG_H 1 */
/*have no <sys/reg.h> header file. so undefine 20070402 dding */
2.
thread-db.c: In function `thread_db_err_str': gdb6.5
thread-db.c:95: `TD_VERSION' undeclared (first use in this function)
[root@dding gdbserver]# gedit config.h
94 #ifdef HAVE_TD_VERSION
95 case TD_VERSION:
96 return "version mismatch between libthread_db and libpthread";
97 #endif
/* Define if TD_VERSION is available. */
/*#define HAVE_TD_VERSION 1 */
/*have no TD_VERSION. so undefine 20070402 dding */
gdb6.1 没有此问题
3.
[root@AT91RM9200DK arm]$./gdbserver 192.168.0.12:2345 mainparacarm gdb6.5
./gdbserver: error in loading shared libraries: libthread_db.so.1: cannot open
[root@AT91RM9200DK arm]$./gdbserver 192.168.0.14:2345 mainparacarm gdb6.1
./gdbserver: error in loading shared libraries: libthread_db.so.1: cannot open shared object file: No such file or directory
我已经加了libthread_db.so.1共享库为什么还打不开呢????共享库和cpu类型有关吗?
gdbserver: error while loading shared libraries: libthread_db.so.1: cannot open
shared object file: No such file or director
****编译GDB的时候搞成静态的就好了.我想编译选项里应该有. 要不你就在Makefile里加上CFLAGS += -static
LDFLAGS += -static
这两个的其中一个应该就可以了,不过还是两个都加上吧.
***/lib there is no libthread_db.so.1 Can i use nfs to copy libthread_db.so.1 to /lib? But now i cannot find this file, and is there any for cross 3.3.2?
libpthread-0.8.so
libpthread.so libpthread.so.0 libresolv-2.1.3.so
libresolv.so.2 libstdc++.a.2.10.0 libtermcap.so.2
[root@AT91RM9200DK arm]$cp libthread_db-1.0.so libthread_db.so.1
[root@AT91RM9200DK arm]$cp libthread_db.so.1 /lib/
[root@AT91RM9200DK arm]$./gdbserver 192.168.0.12:2345 mainparacarm
./gdbserver: /lib/libc.so.6: version `GLIBC_2.2' not found (required by /lib/li)
难道目前的gdb 6.5 版本太高,需要内核版本和交叉编译器与之匹配?实在不行,就试试低版本的gdb
参考文档
http://blog.chinaunix.net/u/27802/showart_211833.html
http://litttlebylittle.bokee.com/5803108.html
http://www.blogcn.com/u/93/99/litcatfish/index.html
相关文章:

Android开发技巧——去掉TextView中autolink的下划线
我们知道,在布局文件中设置textview的autolink及其类型,这时textivew上会显示link的颜色,并且文字下面会有一条下划线,表示可以点击。而在我们在点击textview时,应用将根据我们所设置的类型跳转到对应的界面。但是有时…

【算法导论】冒泡排序 选择排序
冒泡排序: //从大到小 void bubble_sort(int array[],int len) {int i,j,t;for(i0;i<len-1;i){for(j0;j<len-1-i;j){if(array[j]<array[j1]){tarray[j];array[j]array[j1];array[j1]t;} }} } 选择排序: //从大到小 void select_sort(int a…

监控平台zabbix高级配置
2019独角兽企业重金招聘Python工程师标准>>> 12月26日任务 19.12 添加自定义监控项目 19.13/19.14 配置邮件告警 19.15 测试告警 19.16 不发邮件的问题处理 添加自定义监控项目 zabbix可以自定义监控项目,满足个性化的需求。例如网站注册量、访问量等具体…

linux内存实际占用分析
作者: 黄永兵/译 出处:51CTO.com 阅读提示:本文是为那些经常疑惑的人准备的,“为什么一个简单的KDE文本编辑器要占用25M内存?”导致大多数人认为许多Linux应用程序,特别是KDE或GNOME程序都象ps报告一样臃肿...【51CTO.com独家译文…

CentOS 6.5 下Vim 配置图解
分享个CentOS 6.5 下Vim 配置图文详解,希望对大家有所帮助。 1. 登录并进入你常用的用户名下,查看其主目录 命令: # su xxx $ cd xxx $ ls -a 2.查看并建立目录和文件 首先看你的主目录~/ 下是否有.vimrc文件,没有就输入指令 $ to…
【ACM】杭电OJ 1106 函数atoi
函数atoi是把字符串转化成整数的函数,头文件为 #include "stdlib.h" e.g. 运行环境:Dev-C 5.11 杭电1106 调用了sort函数,运行的时间相对长一些。 #include "stdio.h" #include "string.h" #include "…

docker-dockerfile
docker文件存储驱动dockerfile镜像构建指令示例dockekr镜像是只读的,对容器修改的内容,一旦容器退出,所有的内容将会丢失。镜像是分层的,最上的一层为读写层(写时复制和用时分配) 文件系统存储驱动…

proc/[pid]/maps 文件解释
proc/[pid]/maps 文件解释 查看进程的虚拟地址空间是如何使用的。 该文件有6列,分别为: 地址:库在进程里地址范围 权限:虚拟内存的权限,r读,w写,x,s共享,p私有; 偏移量:库在进程里…

【ACM】UVa 1339
【题目】:给定两个长度相同且不超过100的字符串,判断是否能把其中一个字符串的各个字母重排,然后对26个字母做一一映射,使得两个字符串相同。输入两个字符串,输出“YES”或者“NO”。 【分析】:既然字母可…

springBoot PUT请求接收不了参数的解决办法
2019独角兽企业重金招聘Python工程师标准>>> 做项目的时候,想把接口写标准点,于是在更新内容的时候采用put提交内容,但是提交内容时总是获取不到参数,总是选择参数为null。 首先贴出我的put的方法控制器的代码 和之前的…

七牛云内容审核服务被选为「上海首批人工智能创新产品」
近日,上海人工智能应用场景建设实施计划正式发布,这是全国首次面向人工智能应用场景需求的征集计划。上海 10 大人工智能应用场景、19 个具体点位需求和 60 个人工智能创新产品集中首发,其中,上海七牛信息技术有限公司(…

linux动态库命名规则
说道“动态库版本兼容”,很多人头脑中首先蹦出的就是“Dll Hell”。啊,这曾经让人头疼的难题。时至今日,这个难题已经很好地解决了。 在进一步讨论之前来思考一个问题:Linux下为什么没有让人头痛的“DllHell”? 回答…

如何在同一系统里同时启动多个Tomcat
需要在同一系统里启动多个tomcat,应该怎么处理? tomcat是个服务程序,需要占用几个通讯端口,所以默认情况是不能启动多个tomcat,如果要启动多个tomcat,需要修改配置文件,通过在配置文件设置不同的通讯端口就可以做到.文件 %TOMCAT_HOME%/conf…

【ACM】Uva 455
【题目】:如果一个字符串可以由某个长度为k的字符串重复多次得到,则称该串以k为周期。输入一个长度不超过80的字符串,输出其最小正周期。 注意以下几点: 1、它的最小正周期一定可以被它的长度整除。 2第一个大循环下 i 可以等于…

前端自动化构建工具webpack (二)之css和插件加载总结
1. webpack只识别js文件,其他文件都需要转换成js文件。所有文件都是模块; 2. css解析 css需要css-loader ---》style-loader -----》less-loader less文件还需要less-loader (注意书写顺序) 3. plugins:他是一个数组&#…

使用command对象操作数据库
1.Command对象查询数据库 protected void Button1_Click(object sender, EventArgs e){//读取web.config节点配置string strcon ConfigurationManager.ConnectionStrings["testjm"].ConnectionString;//实例化sqlConnection对象SqlConnection con new SqlConnectio…

浅析C语言之uint8_t / uint16_t / uint32_t /uint64_t
一、C语言基本数据类型回顾 在C语言中有6种基本数据类型:short、int、long、float、double、char 1、数值类型 1)整型:short、int、long 2)浮点型:float、double 2、字符类型:char 二、typedef回顾 …

【ACM】UVa 489 刽子手游戏(自顶向下)
【题目】 Hangman Judge是一个猜英文单字的小游戏(在电子字典中常会看到),游戏规则如下: 1、答案单字写在纸上(每个字元一张纸),并且被盖起来,玩家每次猜一个英文字元(le…

ssh远程执行多个命令
shell远程执行: 经常需要远程到其他节点上执行一些shell命令,如果分别ssh到每台主机上再去执行很麻烦,因此能有个集中管理的方式就好了。一下介绍两种shell命令远程执行的方法。 前提条件: 配置ssh免密码登陆 对于简单的命令&am…

【ACM】魔方矩阵
输出魔方矩阵 1、将1放在第一行中间一列; 2、从2开始直到nn止各数依次按下列规则存放;每一个数存放的行比前一个数的行数减1,列数加1; 3、如果上一个数的行数为1,则下一个数的行数为n(指最下一行&#x…

iOS 秒数转换成时间,时,分,秒
//转换成时分秒 - (NSString *)timeFormatted:(int)totalSeconds{ int seconds totalSeconds % 60; int minutes (totalSeconds / 60) % 60; int hours totalSeconds / 3600; return [NSString stringWithFormat:"%02d:%02d:%02d",hours, minutes,…

charles和Fiddler感觉哪个更好用
1.fiddler还可以抓HTTPS的包,解析出来都可以2.charles更直观,可能是我先用charles的缘故。charles遍历一个站点,可以右键另存,保存全站文件资源。扒站首选, charles也可以抓https,我改游戏也是抓的https包

systemd用法
一、开机启动 对于那些支持 Systemd 的软件,安装的时候,会自动在/usr/lib/systemd/system目录添加一个配置文件。 如果你想让该软件开机启动,就执行下面的命令(以httpd.service为例)。 $ sudo systemctl enable http…

C#实现php的hash_hmac函数
from:http://blog.csdn.net/ciaos/article/details/12618487 PHP代码示例如下<?php $res1 hash_hmac("sha1","signatureString", "secret");echo $res1."\n";//ee1b654aa861c41fd5813dc365ef106c9801f8f6echo base64_encode($res…
【ACM】杭电OJ 2015
注意输出格式!!!! #include <iostream> #include <cstring> using namespace std; int main () {int m,n,i,sum,flag;while(cin>>n>>m){sum0;flag0;for(i1;i<n;i){sum(2*i);flag;if(flagm){sum/m;cou…

AGC002[BCDEF]题解
F是计数于是就做(kan ti jie)了 B - Box and Ball 模拟一下 每个盒子开一个d表示有的球数 可能存在红球的打个标记 传递一下就行了 #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define inf 2002…

物联网11种通信协议
今天的网络通信技术也是日新月异,有众所周知的WIFI、Bluetooth、Zigbee、2G、3G、4G蜂窝网络,也有新兴的LiFi、AirGig、量子通信等,更有物联网产业爆发前夜,市场衍生出来的一些比较有前景的通信技术,如以窄带物联网NB&…

php 数组的使用
2019独角兽企业重金招聘Python工程师标准>>> 一、字符串和对象,数组之间的相互转换 public function index(){$product array();$product["name"] "apple";$product["price"] 6000;$products array();$products[] $pr…

【ACM】图像旋转
逆时针 //图像旋转 #include <iostream> #include <algorithm> #include <cstring> #include <cstdio> using namespace std; int main () {int a[105][105];int m,n,i,j;while(scanf("%d%d",&n,&m)!EOF)//n行m列 {for(i0;i<n;i…
do一下来了一个redux
导语 一开看redux的时候还是比较蒙的,感觉比较绕,但是又好像是那么回事,接触一个新概念的时候可能都是如此,多去接触就熟悉了,今天就来分享下redux的三大核心为什么就能如此神奇的施展魔法,干撸完源码&…