Category Archives: Laboratory

Linux系统全方位调试与维护工具整理

俗话说:“工欲善其事,必先利其器。”

在多年维护和开发Linux服务器的过程中,我接触了一些常用的开发和调试工具。本文将这些工具整理在一起,便于指导读者选择正确的工具。文中对于工具只有简单的介绍,需要详细的用法可以--help或自行Google。

本文针对的环境是CentOS/RedHat服务器 (需要epel库)。其他发行版应该也有相应的命令,但我没有一一测试。

系统管理

  • ssh: 远程登录
  • last, lastlog: 查看用户登录历史
  • cron: 设置周期运行的定时任务
  • at: 设置运行一次的定时任务
  • screen/nohup: 在后台运行程序,程序不会随着shell关闭而退出
  • rsync: 文件夹同步
  • pv: 文件拷贝(重定向方法,含进度条,但会丢失文件的权限信息)
  • lshw: 显示硬件信息
  • lscpu: 显示CPU信息
  • dmesg: 显示系统日志

资源监控

通过查看系统资源使用,快速定位性能瓶颈与系统异常。

  • 内存
    • free: 当前内存使用情况
  • 硬盘
    • df: 硬盘空间使用情况
    • iotop: 磁盘IO使用实时监视
    • ncdu: 分析文件夹使用的磁盘大小
  • CPU
    • 见进程调试
  • 网络
    • nethogs: 监控当前活跃的网络连接
    • nload: 主机上传/下载流量监控
  • 综合
    • glances: 系统资源、进程查看工具(我的最爱)
    • linux-dash/pyDash: 系统资源监控(基于网页)

内核调试

  • perf
  • kgdb: 内核调试工具
  • lsmod: 显示所有被使用的module
  • vmstat

进程调试

  • ps: 查看当前进程
  • top: 查看当前进程以及进程资源占用情况(动态更新)
  • htop: top替代工具,信息丰富,可以查看树状进程信息
  • pidof: 按进程名查PID
  • skill: kill的升级版,通过名字(而不是PID)kill进程

文件调试

  • tree: 将文件夹按树形显示
  • lsof: 显示当前程序文件(包括虚拟文件)打开情况
  • mc: Midnight Commander知名文件管理器

网络调试

  • curl: 发送HTTP, REST请求
  • tcpdump: 抓包工具,通过自带的与或非逻辑可以组合成复杂的过滤条件
  • wireshark: 类似tcpdump的老牌抓包神器
  • nmap: 网络开放端口扫描
  • ip: 网络管理工具集
  • fping: ping升级版
  • netstat
  • nicstat
  • iptraf

程序调试

  • gdb: gnu项目下的代码调试器
  • strace: 打印出进程调用了哪些系统调用。
  • ldd: 查看程序依赖库。
  • objdump: 打印目标码和反汇编结果。
  • xargs: 读取输入流并将读取的文本作为指定命令行工具的命令行参数。
  • ipcs: 显示进程间通信设施状态

其他工具

  • vim/nano: 文本编辑器
  • grep, sed: 文本信息处理
  • awk: 表处理,很强大的二维表处理功能,可以对格式化文本输出进行处理
  • jq: 命令行的json查询和格式化工具,适合查看REST接口的输出

References

[1] 用十条命令在一分钟内检查Linux服务器性能, http://www.infoq.com/cn/news/2015/12/linux-performance
[2] Linux Performance, Brendan D. Gregg, http://www.brendangregg.com/linuxperf

【C语言深入】陷阱:数组溢出导致内存被意外修改

C语言的指针在提供编程便利的同时,却带来了很多潜在的内存安全问题。见以下例子:

#include <stdio.h>

int main() {

	char string_buff[12];
	unsigned int i_not_zero = 0xFF;

	sprintf(string_buff, "Hello,world!");

	printf("i = %x\r\n", i_not_zero);

	return 0;
}

该程序(不正确)的输出为:

i = 0

上述代码使用sprintf修改了string_buff指针所指向的char型数组。但是由于在申明数组长度的时候,没有考虑到字符串结束符’\0’,所以实际写入时不慎篡改了下一位内存地址的内容(此例中为i_not_zero, 使用MinGW gcc)。在实际程序中,此类bug一般很难被发现,尤其是还有其他程序在正常修改该值时,一般先会去排查和该变量有关的程序。

这只是一个因为不慎所导致的内存溢出问题,而在一些极端的黑客代码中,经常会见到通过内存变量和函数的指针地址反向访问、修改堆栈,从而获得系统的权限。可见指针作为C语言的一个重要(但是晦涩的)组成部分,无形中降低了系统的可靠性和安全性,需要挑战programmer的debug能力。

【C语言深入】陷阱:数组指针作为函数参数返回

再来看一个指针问题,同样的来自一个本科生的代码。这段代码想要实现将一个全是小写字母的字符串转换成对应的大写字母字符串:

char *covert_to_upper_case(char *string) {
    char p[100];
    int i = 0;
    
    for(; i < strlen(string); i++ ) {
            p[i] = string[i] - ('a' - 'A'); 
    }
    p[i] = '\0';
    
    return p;
    
}

然而这段代码没有能实现期望的功能。原因如下:

  1. 主程序调用convert_to_upper_case()函数后,堆栈为p分配了内存空间;
  2. 函数体正确修改了p对应字符数组的内容,并将p的首地址作为指针返回;
  3. 函数返回后,所有临时变量从堆栈中弹出,包括p[100];
  4. 主程序得到返回的指针,对其进行解析。然而指针指向的字符数组此时已经从堆栈中弹出,解析后的数据无法被定义。

要想正确实现对应的功能,应该将目标指针作为额外参数传递给该函数,并由上层调用者提供内存空间的创建。当然也可以使用malloc()将内存分配在堆中,但是需要注意使用对应的free()释放空间,否则会有内存泄露的问题。

【C语言深入】指针的一个错误赋值

关于指针总是有说不完的故事。

最近给本科的学生带Embedded System课程设计,遇到了一个非常奇怪的bug。有一段代码需要实现I2C通信,核心代码已经由软件库提供了,学生只需要设置结构体后调用API即可。一个学生的代码是这样的:

struct I2C_CONFIG {
  // ...
  char *i2c_buff;
  int length;
  // ...
};

struct I2C_CONFIG cfg;
char *i2c_buff;

void I2C_init() 
{
  // ...
  cfg.buff = i2c_buff;
  cfg.length = sizeof(buff);
  // ...
}

void I2C_send(new_buff)
{
  // ...
  i2c_buff = new_buff;
  I2C_MasterTransferData(LPC_I2C1, cfg);
  // ...
}

初看一下没有什么问题:在I2C_init()函数中首先对结构体cfg进行初始化,而在I2C_send()函数中设置了需要发送的数据指针,之后使用I2C的API发送数据。

因为代码一直无法实现期望的功能,我又仔细看了一下其中的蹊跷。我注意到,这段代码中使用了一个中间变量:char *i2c_buff。在I2C_init()中虽然将cfg.buff指向了i2c_buff,但是因为cfg.buff本身也是指针变量,而非”指向指针的指针”,所以这里只实现了简单的按值传递,即将i2c_buff的值 (初始值为0) 赋给了cfg.buff。之后虽然在I2C_send()中修改了临时变量i2c_buff指向的位置,但却没有影响到cfg.buff中的内容,cfg.buff依然指向之前i2c_buff初始化时指向的内存地址,所以需要发送的缓冲指针new_buff其实并没有传递给之后的I2C_MasterTransferData()函数!为了解决这个问题,必须将更改后的i2c_buff的值再次赋给cfg.buff,即:

void I2C_send(new_buff)
{
  // ...
  i2c_buff = new_buff;
  cfg.buff = i2c_buff;
  I2C_MasterTransferData(LPC_I2C1, cfg);
  // ...
}

另外这段代码还有一个不容易注意的bug,就是在I2C_init()中使用了sizeof()来判断buffer的大小。因为sizeof()函数得到的只是数据类型的大小,所以对于指针char *i2c_buff来说,sizeof(i2c_buff) = 4,而不会返回buffer的实际大小。指针的大小并不等于指针指向缓冲的大小!

【RPi树莓派使用指南】树莓派官方7寸屏入门指南

1. 引子

在树莓派官方触摸屏发布之前,市场上可用的屏幕有以下三种:

  • 直接和GPIO插口对接的屏幕,使用SPI与CPU进行通信。需要特殊的驱动程序将framebuffer的内容发送到LCD控制器上,一般带有触屏功能,大小以3.5寸为主流。受限于SPI通信速度,刷新速率不高;
  • 专用USB接口的屏幕,如RoboPeak Mini USB Display。这类屏幕通过USB连接,需要本地运行驱动程序;
  • 通用LCD屏幕,通过HDMI和树莓派连接。因其通用性不需要特殊的驱动程序,但是很多都不支持触屏功能,而且都需要额外的转接板,体积较大;

rpilcd-front-with-base
▲ 图.  树莓派官方7寸屏实拍

我自己的需求是将树莓派作为信息显示中心,在屏幕上显示我的HP服务器的运行信息,另外提供一些快捷的传感器监控和控制操作接口。最初一直在官方屏幕和HDMI屏幕之间犹豫,最后还是选择了官方触摸屏。归结起来主要有几个原因:

  • 官方屏的LCD模组最有保证,淘宝上的HDMI LCD一般成像质量不高;
  • 官方屏的触摸功能在所有方案中是支持的最好的,有十点电容触摸(目前Raspbian还只支持单点,以后会升级),且不需要额外驱动。而HDMI接口的LCD如果有触摸功能,都需要额外接一根USB用于提供触摸控制;
  • 官方触屏和树莓派3可以直接通过铜柱物理连接,无需额外的驱动电路板。连线也非常少,只需要一根DSI软排线和供电接口即可。

总体上来说,虽然官方屏的价格高了一些,但是却是所有方案中最可靠、简洁的,所以最后也没有多犹豫就从网上下单了。rpilcd-front-without-base
▲ 图. 树莓派官方LCD屏实拍 – 正面

Read more »