Linux快速開發之makefile編譯、以服務形式運行、抓包、調試、分析dump
1 Makefile編寫... 1
1.1 實例解析... 1
1.1.1 makefile文件... 1
1.1.2 包含的makefile.global 3
1.2 使用makefile文件... 5
2 Linux編譯... 5
2.1 Linux編譯步驟... 5
3 Linux調試gdb. 6
4 以服務的形式在linux上運行程序... 7
4.1 執行步驟... 7
4.2 安裝服務腳本ivmsservice.sh. 8
5 linux抓包tcpdump與wireshark. 18
5.1 安裝卸載tcpdump. 18
5.2 tcpdump抓包到文件... 18
5.3 分析抓包文件... 19
6 分析dump文件... 19
1 Makefile編寫
Makefile 是一種linux編譯程序的命令集合;通過makefile對程序進行編譯和鏈接;
1.1 實例解析
1.1.1 makefile文件
下面是一個完整的makefile的實例;
#定義C語言文件編譯命令變量,gcc是編譯命令 –o0表示禁用優化,後面是參數
CC = gcc -o0
#定義C++文件編譯命令變量,g++是編譯命令 –o0表示禁用優化,後面是參數
CPP = g++ -o0
#定義make的變量
MAKE = make
#定義去除字符串首尾空格的變量
STRIP = strip
#定義根目錄的變量,..表示makefile文件所在目錄的上一級
TOP_DIR = ..
#包含文件,像是c++裏的頭文件,裏面有一些定義
include Makefile_cmsws_ass_stub_linux.global
#定義靜態庫的變量,$(LD_STATIC_LIB)在global中的變量
LD_STATIC_LIB := $(LD_STATIC_LIB)
#定義動態庫的變量,$( LDLIBS)在global中的變量
LDLIBS := $(LDLIBS) -lgsoap -lhlog -liconv -lssl -lcrypto
#定義生成目標文件,名稱和路徑
TARGET = $(LIB_DIR)/libcmsws_ass_stub.so
#定義變量生成中間對象文件,
OBJ_DIR := $(TOP_DIR)/obj
#定義創建對象文件夾的命令,shell語句用來判斷是否存在,並創建對象文件夾
MAKE_OBJ_DIR := $(shell if [ ! -d $(OBJ_DIR) ]; then mkdir $(OBJ_DIR) ; fi )
#定義創建對象文件夾的子文件的命令,
MAKE_OBJ_SUBDIR := $(shell for sub_dir in $(VPATH); do if [ ! -d $(OBJ_DIR)/$$sub_dir ]; then mkdir $(OBJ_DIR)/$$sub_dir; fi ; done; )
#定義源文件變量,$(VPATH)是程序目錄,*意思是獲取程序目錄下的所有文件名,最後得到的就是所有文件名組成的字符串,以空格隔開;
SOURCES := $(foreach dir, $(VPATH), $(wildcard $(dir)/*))
#filet是過濾出所有的c文件名稱,篩選出.c文件,也就是C語言文件
C_SRCS := $(filter %.c, $(SOURCES))
#filet是過濾出所有的c++文件,篩選出C++文件
CPP_SRCS := $(filter %.cpp, $(SOURCES))
#定義源文件變量,所有的C文件和C++文件
SRCS := $(C_SRCS) $(CPP_SRCS)
#定義對象文件變量,包含所有的對象文件,patsubst是字符串替換函數,將文件名字的後綴都改成.o,表示生成的對象文件的名稱patsubst是命令,%.c表示要替換的文件名通配符,%.o表示要替換後的文件名通配符;
OBJS := $(patsubst %, $(OBJ_DIR)/%, $(patsubst %.c, %.o, $(patsubst %.cpp, %.o, $(SRCS))))
#定義編譯規則,目標:源文件 編譯命令 ;生成c語言的對象文件,$(CC)是gcc編譯命令;格式爲 目標($(OBJ_DIR)/%.o):生成目標的源文件(%.c) 編譯命令($(CC)) 編譯參數(-g3 表示加入調試信息,$(CFLAGS)包含一些參數和頭文件);如果頭文件或者源文件比.o文件要新,則會重新編譯;
$(OBJ_DIR)/%.o:%.c
$(CC) -g3 -c $(CFLAGS) $< -o $@
#定義編譯規則,目標:源文件 編譯命令 ;生成c++的對象文件
$(OBJ_DIR)/%.o:%.cpp
$(CPP) -g3 -c $(CPPFLAGS) $< -o $@
#生成目標文件,包含所有的對象文件,動靜態庫
all: $(OBJS)
$(CPP) -g -shared $(OBJS) -fPIC $(LDFLAGS) $(LDLIBS) $(LD_STATIC_LIB) -o $(TARGET)
#生成僞目標文件,用來清除文件不生成真的文件,執行 make –f Makefilename clean 就會清除生成的目標文件和中間對象文件;
clean:
-rm -f $(TARGET) $(OBJS)
1.1.2 包含的makefile.global
CC = gcc
CPP = g++
MAKE = make
STRIP = strip
##define inc_dir
#定義工程根目錄變量
PROJ_BASE = $(TOP_DIR)
#定義頭文件目錄變量
INC_DIR = $(TOP_DIR)/cmsws_alarm_server_service_stub\
$(TOP_DIR)/cmsws_lib/cmsws \
$(TOP_DIR)/cmsws_lib/hlog \
$(TOP_DIR)/cmsws_lib/iconv \
$(TOP_DIR)/cmsws_platform/windows \
$(TOP_DIR)/cmsws_lib/cmsws_ass_stub\
$(TOP_DIR)/cmsws_alarm_server_service_stub/service_stub \
$(TOP_DIR)/cmsws_alarm_server_service_stub/ass_stub_interface \
#源文件路徑
VPATH+=$(TOP_DIR)/cmsws_platform/windows $(TOP_DIR)/cmsws_alarm_server_service_stub/service_stub $(TOP_DIR)/cmsws_alarm_server_service_stub/ass_stub_interface $(TOP_DIR)/cmsws_alarm_server_service_stub
#set so compile options
#定義so文件的編譯的參數變量
SOFLAGS = -shared -fPIC
#-fstack-check -fvisibility=hidden
#定義C文件的編譯的參數變量
CFLAGS := $(CFLAGS) -m64 $(SOFLAGS) $(patsubst %, -I%, $(INC_DIR))
#定義C++文件的編譯的參數變量
CPPFLAGS := $(CFLAGS) -fPIC -ftemplate-depth-128
#定義動態庫的目錄變量
LIB_DIR = $(TOP_DIR)/linux_lib_x64
#定義靜態庫的目錄變量
STATIC_LIB_DIR = $(TOP_DIR)/linux_lib_x64
#定義動靜態庫的編譯的參數變量
LDFLAGS := $(LDFLAGS) $(SOFLAGS) -Wl,--hash-style=both -Wl,-Bsymbolic -Wl,--enable-new-dtags -Wl,-rpath,./
LDLIBS := $(LDLIBS) -lpthread -lstdc++ -lrt $(patsubst %, -L%, $(LIB_DIR))
LD_STATIC_LIB := $(LD_STATIC_LIB) $(patsubst %, -L%, $(STATIC_LIB_DIR))
1.2 使用makefile文件
使用上述makefile文件作爲模板,修改源文件目錄、頭文件目錄,生成的目標名稱,包含的動靜態庫,動靜態庫路徑;就可以使用這個模板輕鬆編譯linux程序;
2 Linux編譯
2.1 Linux編譯步驟
(1)採用secureCRT軟件,登陸linux服務器,進入makefile所在文件夾,執行makefile文件。如下圖所示;編譯中出現錯誤則根據提示修改;
(2)編譯成功後,程序不一定能夠執行;需要執行ldd –r libalarm_logic_unit.so,查看目標文件libalarm_logic_unit.so是否缺少庫文件或者未定義的函數;相當於window下的depend工具;
根據提示添加缺少的庫文件,修改錯誤,未識別的符號有的是一些亂碼,可用下列命令來查看具體的原因;c++filt _ZNSbIhSt11char_traitsIhESaIhEE4_Rep20_S_empty_rep_storageE
(3)軟連接,一些開發者提供的庫文件後綴一般是.so.1.0.0的格式,這樣makefile無法識別,需要通過ln –s libssl.so.1.0.0 libssl.so 創建軟連接libssl.so 指向libssl.so.1.0.0;軟連接相當於C++中引用的概念,是個別稱;
3 Linux調試gdb
(1) gdb alarm_logic_unit 。 gdb加程序名稱,表示調試該程序;
(2) b 。加函數名稱或者 源文件名稱:行號,來加斷點;
(3) r 。運行程序進行調試;執行到斷點處會停住;
(4) p 。變量名稱 來查看變量的值;
(5) c 。繼續執行到下一個斷點;
(6) n 。單步執行;
(7) l 。查看下面要執行的幾行代碼;
(8) q退出gdb;
4 以服務的形式在linux上運行程序
4.1 執行步驟
(1)複製文件工具
WinSCP是一款可視化的文件複製工具,可以方便的將windows系統中的文件複製到linux系統中;如下圖所示,現在windows系統中登陸linux系統,然後通過拖動的方式將文件來回複製;
(2)編寫腳本程序
編寫六個腳本文件*.sh文件,文件的內容如下;./ivmsservice.sh是安裝卸載服務的文件,待會介紹;
安裝服務腳本install.sh:./ivmsservice.sh install alarm_logic_unit
卸載服務腳本uninstall.sh:./ivmsservice.sh uninstall alarm_logic_unit
啓動服務腳本start.sh:service alarm_logic_unit start
停止服務腳本stop.sh:service alarm_logic_unit stop
服務狀態腳本status.sh:service alarm_logic_unit status
重啓服務腳本restart.sh:service alarm_logic_unit restart
(3)轉換文件格式,在windows環境下編寫的文件在linux下執行會因爲換行符的不同而無法執行,window下是/n/r,linux下是/n,所以需要進行格式轉換;先用命令:sudo yum install dos2unix 下載安裝dos2unix程序;然後在用dos2unix *.sh命令轉換所有的sh文件格式;
(4)先執行安裝腳本,在執行啓動腳本,再用命令ps –ef 顯示電腦的進程,看是否都安裝啓動成功;執行statu.sh腳本查看狀態,執行stop.sh 停止服務,執行uninstall.sh卸載服務;如果無法停止服務,可以用kill 進程id(12897)命令,通過進程id號殺死進程;
4.2 安裝服務腳本ivmsservice.sh
將程序安裝位服務,實際上是在/etc/init.d路徑下加入一個執行腳本,執行install.sh腳本後,會在/etc/init.d中加入一個腳本文件,如下圖所示;文件的名稱和服務程序的名稱相同;內容實際就是將ivmsservice.sh文件的內容替換掉服務名稱、服務文件路徑等;
下面將詳細介紹服務安裝腳本的內容:
#第一行表示shell腳本採用的解釋器,是在bin路徑下的bash解釋器
#!/bin/bash
# chkconfig: 2345 58 74
# description: IVMS_SERVICE_NAME is a iVMS8200 Service
# common function,表示引用/etc/init.d/functions這個文件中的函數
. /etc/init.d/functions
# service bash flag 標識符,用於標記是否安裝服務;0表示未安裝;
IVMS_SERVICE=0
#設置dump文件路徑
CORE_DUMP_DIR=/var/ivms8200_core
DAEMON_COREFILE_LIMIT=unlimited
#設置服務安裝文件路徑
SVC_SYS_DIR=/etc/init.d
#設置服務執行文件路徑
SVC_PROG=IVMS_SERVICE_PROG
#設置服務的名字
SVC_NAME=IVMS_SERVICE_NAME
#設置服務文件的路徑
SVC_DIR=IVMS_SERVICE_DIR
#設置鎖文件的路徑
SVC_LOCK_FILE=/var/lock/subsys/$SVC_PROG
SVC_PID_FILE=/var/run/$SVC_NAME.pid
# 安裝服務函數install service, $1 參數1,service name, $2參數2,executable name
InstallSvc()
{ #判斷參數1是否存在
if [ -z "$1" ]; then
echo $"service name is needed by ivmsservice."
return -1
fi
#將參數1設置爲變量SVC_NAME的值
SVC_NAME=$1
#將參數2 和服務名稱的組合${2:-$SVC_NAME}作爲執行變量的值
SVC_PROG=${2:-$SVC_NAME}
#將當前路徑設置爲服務的路徑
SVC_DIR=`pwd`
#將系統服務目錄和文件名字組成的路徑作爲系統服務文件
SVC_SYS_FILE=$SVC_SYS_DIR/$SVC_NAME
#如果$SVC_DIR/$SVC_PROG不爲常規文件,則報錯返回
if [ ! -f $SVC_DIR/$SVC_PROG ]; then
echo $"$SVC_DIR/$SVC_PROG does not exist."
return -1
fi
#提示正在安裝
echo $"installing $SVC_NAME service, executable file $SVC_DIR/$SVC_PROG ..."
#將配置文件中suid_dumpable的內容設置爲1,表示允許產生dump文件
# set suid_dumpable on
if [ -e /proc/sys/kernel/suid_dumpable ]; then
echo 1 > /proc/sys/kernel/suid_dumpable
else
echo 1 > /proc/sys/fs/suid_dumpable
fi
#判斷$CORE_DUMP_DIR文件夾是否存在,不存在則創建,並設置dump文件名格式,%e-%p-%t分別表示文件名稱加入執行文件名稱,添加進程的pid,添加時間;最後生成的文件名字形如:core-alarm_logic_unit-12149-20170820
# create core directory
if [ -d $CORE_DUMP_DIR ]; then
echo $"$CORE_DUMP_DIR/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
else
mkdir $CORE_DUMP_DIR
echo $"$CORE_DUMP_DIR/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
fi
# config sysctl to enable core設置系統允許產生dump文件$?表示上一條命令的返回值>>表示以追加的方式在/etc/sysctl.conf中加入兩個變量,表示允許刪除dump的bool值和dump文件的文件名格式;
grep "$CORE_DUMP_DIR/core-%e-%p-%t" /etc/sysctl.conf
if [ $? -ne 0 ]; then
echo $"fs.suid_dumpable = 1" >> /etc/sysctl.conf
echo $"kernel.core_pattern = $CORE_DUMP_DIR/core-%e-%p-%t" >> /etc/sysctl.conf
fi
#下面這段代碼是將本腳本本身替換掉一些東西后,在輸入到$SVC_SYS_FILE文件中,即在SVC_SYS_DIR=/etc/init.d目錄中創建一個與服務名字相同的名稱的腳本文件,內容爲該腳本替換後的腳本文件;
# create service bash
#$0表示參數0,即ivmsservice.sh本身,第一句是讀取ivmsservice.sh中的數據到緩衝區,sed是文本編輯命令,將文件中的數據讀取到緩衝區進行編輯;%作爲分界符;-e表示多行編輯;冒號中s表示字符替換,g表示全部替換;>表示將緩衝區中的內容保存到文件中;第一行意思是讀取文件中的數據,將IVMS_SERVICE=0全部替換成IVMS_SERVICE=1,這裏只是字符替換;第二行是將變量$SVC_PROG全部替換掉IVMS_SERVICE_PROG,第四行是先替換,然後將緩衝區中的數據輸入到變量$SVC_SYS_FILE標記的文件中;用VMS_SERVICE=1替換掉IVMS_SERVICE=0 ;
sed -e "s%IVMS_SERVICE=0%IVMS_SERVICE=1%g" $0 | \
sed -e "s%IVMS_SERVICE_PROG%$SVC_PROG%g" | \
sed -e "s%IVMS_SERVICE_NAME%$SVC_NAME%g" | \
sed -e "s%IVMS_SERVICE_DIR%$SVC_DIR%g" > $SVC_SYS_FILE
#chmod給文件賦予權限
chmod u+x $SVC_SYS_FILE
chmod u+x $SVC_DIR/$SVC_PROG
#chkconfig 的作用是註冊服務
chkconfig --add $SVC_NAME
echo $"install $SVC_NAME service successfully."
return 0
}
# uninstall service, $1 service name
UninstallSvc()
{
#判斷參數1是否存在
if [ -z "$1" ]; then
echo $"service name is needed by ivmsservice."
return -1
fi
SVC_NAME=$1
SVC_SYS_FILE=$SVC_SYS_DIR/$SVC_NAME
echo $"uninstalling $SVC_NAME service ..."
# rm service bash註銷服務,刪除服務腳本,;
if [ -f $SVC_SYS_FILE ]; then
chkconfig --del $SVC_NAME
rm -f $SVC_SYS_FILE
else
warning $"$SVC_NAME service does not exist."
fi
echo $"uninstall $SVC_NAME service successfully."
return 0
}
# start service啓動服務
StartSvc()
{
echo $"starting $SVC_NAME service ..."
# set core unlimited, replaced by DAEMON_COREFILE_LIMIT
#ulimit -c unlimited
# create lockfile, run program
touch $SVC_LOCK_FILE
cd $SVC_DIR
daemon --pidfile=$SVC_PID_FILE $SVC_DIR/$SVC_PROG -service
if [ $? -eq 0 ]; then
# created by the program
#pidof $SVC_DIR/$SVC_PROG > $SVC_PID_FILE
echo $"start $SVC_NAME service successfully."
return 0
else
echo $"start $SVC_NAME service failure."
return -1
fi
}
# get service status
IsSvcRunning()
{
local pid
__pids_var_run $SVC_NAME $SVC_PID_FILE
[ -n "$pid" ] && return 0 || return 1
}
# stop service
StopSvc()
{
echo $"stopping $SVC_NAME service ..."
# remove lockfile刪除lockfile文件,然後停止服務
rm -f $SVC_LOCK_FILE
# wait for exit
local i RC
for (( i = 0; i < 10; i++ )); do
if IsSvcRunning; then
sleep 1
else
break
fi
done
#如果沒有停止,則強制殺死進程
if [ $i -eq 10 ] && IsSvcRunning; then
killproc -p $SVC_PID_FILE $SVC_NAME
RC=$?
else
rm -f $SVC_PID_FILE
RC=0
fi
if [ $RC -eq 0 ]; then
echo $"stop $SVC_NAME service successfully."
return 0
else
echo $"stop $SVC_NAME service failure."
return -1
fi
}
# restart service
RestartSvc()
{
echo $"restarting $SVC_NAME service ..."
StopSvc
if [ $? -eq 0 ]; then
StartSvc
if [ $? -eq 0 ]; then
echo $"restart $SVC_NAME service successfully."
return 0
fi
fi
echo $"restart $SVC_NAME service failure."
return -1
}
RETVAL=0
if [ $IVMS_SERVICE -eq 0 ]; then
# setup
case $1 in
install | i)
InstallSvc $2 $3
RETVAL=$?
;;
uninstall | u)
UninstallSvc $2
RETVAL=$?
;;
*)
echo $"Usage: $0 {i, install NAME [EXEC] | u, uninstall NAME}"
;;
esac
else
# service
case $1 in
start | r)
StartSvc
RETVAL=$?
;;
stop | p)
StopSvc
RETVAL=$?
;;
restart | e)
RestartSvc
RETVAL=$?
;;
status | s)
status -p $SVC_PID_FILE $SVC_NAME
RETVAL=$?
;;
status2)
IsSvcRunning && echo $"$SVC_NAME service is running." || echo $"$SVC_NAME service has been stopped."
;;
*)
echo $$"Usage: $0 {start|stop|status|restart}"
;;
esac
fi
exit $RETVAL
5 linux抓包tcpdump與wireshark
5.1 安裝卸載tcpdump
安裝tcpdump 命令:sudo yum install tcpdump
移除tcpdump命令:sudo yum remove tcpdump
5.2 tcpdump抓包到文件
Wireshark(以前是ethereal)是Windows下非常簡單易用的抓包工具。但在Linux下很難找到一個好用的圖形化抓包工具。還好有Tcpdump。我們可以用Tcpdump + Wireshark 的完美組合實現:在 Linux 裏抓包,然後在Windows 裏分析包。
tcpdump tcp -i eth1 -t -s 0 -c 100 and dst port ! 22 and src net 192.168.1.0/24 -w ./target.cap
(1)tcp: ip icmp arp rarp 和 tcp、udp、icmp這些選項等都要放到第一個參數的位置,用來過濾數據報的類型;
(2)-i eth1 : 只抓經過接口eth1的包;
(3)-t : 不顯示時間戳;
(4)-s 0 : 抓取數據包時默認抓取長度爲68字節。加上-S 0 後可以抓到完整的數據包;
(5)-c 100 : 只抓取100個數據包;
(6)dst port ! 22 : 不抓取目標端口是22的數據包;
(7)src net 192.168.1.0/24 : 數據包的源網絡地址爲192.168.1.0/24;
(8)-w ./target.cap : 保存成cap文件,方便用ethereal(即wireshark)分析;
例如抓取tcp端口61616的數據,保存到mq.cap文件,命令如下:
tcpdump tcp port 61616 -w ./mq.cap
5.3 分析抓包文件
直接將mq.cap文件複製到window系統中,然後用wireshark工具打開文件即可;如linux系統有圖形化界面,可以直接在linux系統中,安裝linux版本的wireshark軟件;直接抓包分析,無需用tcpdump;
6 分析dump文件
發生core dump之後, 用gdb進行查看core文件的內容, 以定位文件中引發core dump的行.
gdb [exec file] [core file]
如: gdb ./test test.core
使用gdb 調試方法,首先要在gcc編譯時加入-g選項。
調試core文件,在Linux命令行下:gdb pname corefile。
這樣進入了gdb core調試模式。
追蹤產生segmenttation fault的位置及代碼函數調用情況:
gdb>bt
這樣,一般就可以看到出錯的代碼是哪一句了,還可以打印出相應變量的數值,進行進一步分析。