主頁 >  其他 > GDB除錯-從入門實踐到原理

GDB除錯-從入門實踐到原理

2022-01-06 08:41:41 其他

關注公眾號【高性能架構探索】,回復【pdf】,免費獲取計算機經典資料

在上篇文章中,我們分析了線上coredump產生的原因,其中用到了coredump分析工具gdb,這幾天一直有讀者在問,能不能寫一篇關于gdb除錯方面的文章,今天借助此文,分享一些作業中的除錯經驗,希望能夠幫到大家,

寫在前面

在我的作業經歷中,前幾年在Windows上進行開發,使用Visual Studio進行除錯,簡直是利器,各種斷點等用滑鼠點點點就能設定;大概從12年開始轉Linux開發了,所以除錯都是基于GDB的,本來這篇文章也想寫寫Windows下除錯相關,奈何好多年沒用了,再加上作業太忙,所以本文就只寫了Linux下GDB除錯相關,對于Windows開發人員,實在對不住了😃,

這篇文章,涉及的比較全面,總結了這些年的gdb除錯經驗(都是小兒科😁),經常用到的一些除錯技巧,希望能夠對從事Linux開發的相關人員有所幫助

背景

作為C/C++開發人員,保證程式正常運行是最基本也是最主要的目的,而為了保證程式正常運行,除錯則是最基本的手段,熟悉這些除錯方式,可以方便我們更快的定位程式問題所在,提高開發效率,

在開發程序,如果程式的運行結果不符合預期,第一時間就是打開GDB進行除錯,在對應的地方設定斷點,然后分析原因;當線上服務出了問題,第一時間查看行程在不在,如果不在的話,是否生成了coredump檔案,如果有,則使用gdb除錯coredump檔案,否則通過dmesg來分析內核日志來查找原因,

概念

GDB是一個由GNU開源組織發布的、UNIX/LINUX作業系統下的、基于命令列的、功能強大的程式除錯工具

GDB支持斷點、單步執行、列印變數、觀察變數、查看暫存器、查看堆疊等除錯手段,在Linux環境軟體開發中,GDB是主要的除錯工具,用來除錯C和 C++程式(也支持go等其他語言),

常用命令

斷點

斷點是我們在除錯中經常用的一個功能,我們在指定位置設定斷點之后,程式運行到該位置將會暫停,這個時候我們就可以對程式進行更多的操作,比如查看變數內容,堆疊情況等等,以幫助我們除錯程式,

以設定斷點的命令分為以下幾類:

  • breakpoint
  • watchpoint
  • catchpoint

breakpoint

可以根據行號、函式、條件生成斷點,下面是相關命令以及對應的作用說明:

命令作用
break [file]:function在檔案file的function函式入口設定斷點
break [file]:line在檔案file的第line行設定斷點
info breakpoints查看斷點串列
break [±]offset在當前位置偏移量為[±]offset處設定斷點
break *addr在地址addr處設定斷點
break … if expr設定條件斷點,僅僅在條件滿足時
ignore n count接下來對于編號為n的斷點忽略count次
clear洗掉所有斷點
clear function洗掉所有位于function內的斷點
delete n洗掉指定編號的斷點
enable n啟用指定編號的斷點
disable n禁用指定編號的斷點
save breakpoints file保存斷點資訊到指定檔案
source file匯入檔案中保存的斷點資訊
break在下一個指令處設定斷點
clear [file:]line洗掉第line行的斷點

watchpoint

watchpoint是一種特殊型別的斷點,類似于正常斷點,是要求GDB暫停程式執行的命令,區別在于watchpoint沒有駐留某一行源代碼中,而是指示GDB每當某個運算式改變了值就暫停執行的命令,

watchpoint分為硬體實作和軟體實作兩種,前者需要硬體系統的支持;后者的原理就是每步執行后都檢查變數的值是否改變,GDB在新建資料斷點時會優先嘗試硬體方式,如果失敗再嘗試軟體實作,

命令作用
watch variable設定變數資料斷點
watch var1 + var2設定運算式資料斷點
rwatch variable設定讀斷點,僅支持硬體實作
awatch variable設定讀寫斷點,僅支持硬體實作
info watchpoints查看資料斷點串列
set can-use-hw-watchpoints 0強制基于軟體方式實作

使用資料斷點時,需要注意:

  • 當監控變數為區域變數時,一旦區域變數失效,資料斷點也會失效
  • 如果監控的是指標變數p,則watch *p監控的是p所指記憶體資料的變化情況,而watch p監控的是p指標本身有沒有改變指向

最常見的資料斷點應用場景:定位堆上的結構體內部成員何時被修改,由于指標一般為區域變數,為了解決斷點失效,一般有兩種方法,

命令作用
print &variable查看變數的記憶體地址
watch *(type *)address通過記憶體地址間接設定斷點
watch -l variable指定location引數
watch variable thread 1僅編號為1的執行緒修改變數var值時會中斷

catchpoint

從字面意思理解,是捕獲斷點,其主要監測信號的產生,例如c++的throw,或者加載庫的時候,產生斷點行為,

命令含義
catch fork程式呼叫fork時中斷
tcatch fork設定的斷點只觸發一次,之后被自動洗掉
catch syscall ptrace為ptrace系統呼叫設定斷點

command命令后加斷點編號,可以定義斷點觸發后想要執行的操作,在一些高級的自動化除錯場景中可能會用到,

命令列

命令作用
run arglist以arglist為引數串列運行程式
set args arglist指定啟動命令列引數
set args指定空的引數串列
show args列印命令列串列

程式堆疊

命令作用
backtrace [n]列印堆疊幀
frame [n]選擇第n個堆疊幀,如果不存在,則列印當前堆疊幀
up n選擇當前堆疊幀編號+n的堆疊幀
down n選擇當前堆疊幀編號-n的堆疊幀
info frame [addr]描述當前選擇的堆疊幀
info args當前堆疊幀的引數串列
info locals當前堆疊幀的區域變數

多行程、多執行緒

多行程

GDB在除錯多行程程式(程式含fork呼叫)時,默認只追蹤父行程,可以通過命令設定,實作只追蹤父行程或子行程,或者同時除錯父行程和子行程,

命令作用
info inferiors查看行程串列
attach pid系結行程id
inferior num切換到指定行程上進行除錯
print $_exitcode顯示程式退出時的回傳值
set follow-fork-mode child追蹤子行程
set follow-fork-mode parent追蹤父行程
set detach-on-fork onfork呼叫時只追蹤其中一個行程
set detach-on-fork offfork呼叫時會同時追蹤父子行程

在除錯多行程程式時候,默認情況下,除了當前除錯的行程,其他行程都處于掛起狀態,所以,如果需要在除錯當前行程的時候,其他行程也能正常執行,那么通過設定set schedule-multiple on即可,

多執行緒

多執行緒開發在日常開發作業中很常見,所以多執行緒的除錯技巧非常有必要掌握,

默認除錯多執行緒時,一旦程式中斷,所有執行緒都將暫停,如果此時再繼續執行當前執行緒,其他執行緒也會同時執行,

命令作用
info threads查看執行緒串列
print $_thread顯示當前正在除錯的執行緒編號
set scheduler-locking on除錯一個執行緒時,其他執行緒暫停執行
set scheduler-locking off除錯一個執行緒時,其他執行緒同步執行
set scheduler-locking step僅用step除錯執行緒時其他執行緒不執行,用其他命令如next除錯時仍執行

如果只關心當前執行緒,建議臨時設定 scheduler-lockingon,避免其他執行緒同時運行,導致命中其他斷點分散注意力,

列印輸出

通常情況下,在除錯的程序中,我們需要查看某個變數的值,以分析其是否符合預期,這個時候就需要列印輸出變數值,

命令作用
whatis variable查看變數的型別
ptype variable查看變數詳細的型別資訊
info variables var查看定義該變數的檔案,不支持區域變數
列印字串

使用x/s命令列印ASCII字串,如果是寬字符字串,需要先看寬字符的長度 print sizeof(str)

如果長度為2,則使用x/hs列印;如果長度為4,則使用x/ws列印,

命令作用
x/s str列印字串
set print elements 0列印不限制字串長度/或不限制陣列長度
call printf("%s\n",xxx)這時列印出的字串不會含有多余的轉義符
printf “%s\n”,xxx同上
列印陣列
命令作用
print *array@10列印從陣列開頭連續10個元素的值
print array[60]@10列印array陣列下標從60開始的10個元素,即第60~69個元素
set print array-indexes on列印陣列元素時,同時列印陣列的下標
列印指標
命令作用
print ptr查看該指標指向的型別及指標地址
print *(struct xxx *)ptr查看指向的結構體的內容
列印指定記憶體地址的值

使用x命令來列印記憶體的值,格式為x/nfu addr,以f格式列印從addr開始的n個長度單元為u的記憶體值,

  • n:輸出單元的個數
  • f:輸出格式,如x表示以16進制輸出,o表示以8進制輸出,默認為x
  • u:一個單元的長度,b表示1byteh表示2bytehalf word),w表示4byteg表示8bytegiant word
命令作用
x/8xb array以16進制列印陣列array的前8個byte的值
x/8xw array以16進制列印陣列array的前16個word的值
列印區域變數
命令作用
info locals列印當前函式區域變數的值
backtrace full列印當前堆疊幀各個函式的區域變數值,命令可縮寫為bt
bt full n從內到外顯示n個堆疊幀及其區域變數
bt full -n從外向內顯示n個堆疊幀及其區域變數
列印結構體
命令作用
set print pretty on每行只顯示結構體的一名成員
set print null-stop不顯示’\000’這種

函式跳轉

命令作用
set step-mode on不跳過不含除錯資訊的函式,可以顯示和除錯匯編代碼
finish執行完當前函式并列印回傳值,然后觸發中斷
return 0不再執行后面的指令,直接回傳,可以指定回傳值
call printf("%s\n", str)呼叫printf函式,列印字串(可以使用call或者print呼叫函式)
print func()呼叫func函式(可以使用call或者print呼叫函式)
set var variable=xxx設定變數variable的值為xxx
set {type}address = xxx給存盤地址為address,型別為type的變數賦值
info frame顯示函式堆疊的資訊(堆疊幀地址、指令暫存器的值等)

其它

圖形化

tui為terminal user interface的縮寫,在啟動時候指定-tui引數,或者除錯時使用ctrl+x+a組合鍵,可進入或退出圖形化界面,

命令含義
layout src顯示原始碼視窗
layout asm顯示匯編視窗
layout split顯示原始碼 + 匯編視窗
layout regs顯示暫存器 + 原始碼或匯編視窗
winheight src +5原始碼視窗高度增加5行
winheight asm -5匯編視窗高度減小5行
winheight cmd +5控制臺視窗高度增加5行
winheight regs -5暫存器視窗高度減小5行

匯編

命令含義
disassemble function查看函式的匯編代碼
disassemble /mr function同時比較函式源代碼和匯編代碼

除錯和保存core檔案

命令含義
file exec_file *# *加載可執行檔案的符號表資訊
core core_file加載core-dump檔案
gcore core_file生成core-dump檔案,記錄當前行程的狀態

啟動方式

使用gdb除錯,一般有以下幾種啟動方式:

  • gdb filename: 除錯可執行程式
  • gdb attach pid: 通過”系結“行程ID來除錯正在運行的行程
  • gdb filename -c coredump_file: 除錯可執行檔案

在下面的幾節中,將分別對上述幾種除錯方式進行講解,從例子的角度出發,使得大家能夠更好的掌握除錯技巧,

除錯

可執行檔案

單執行緒

首先,我們先看一段代碼:

#include<stdio.h>

void print(int xx, int *xxptr) {
  printf("In print():\n");
  printf("   xx is %d and is stored at %p.\n", xx, &xx);
  printf("   ptr points to %p which holds %d.\n", xxptr, *xxptr);
}

int main(void) {
  int x = 10;
  int *ptr = &x;
  printf("In main():\n");
  printf("   x is %d and is stored at %p.\n", x, &x);
  printf("   ptr points to %p which holds %d.\n", ptr, *ptr);
  print(x, ptr);
  return 0;
}

這個代碼比較簡單,下面我們開始進入除錯:

gdb ./test_main
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/test_main...done.
(gdb) r
Starting program: /root/./test_main
In main():
   x is 10 and is stored at 0x7fffffffe424.
   ptr points to 0x7fffffffe424 which holds 10.
In print():
   xx is 10 and is stored at 0x7fffffffe40c.
   xxptr points to 0x7fffffffe424 which holds 10.
[Inferior 1 (process 31518) exited normally]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64

在上述命令中,我們通過gdb test命令啟動除錯,然后通過執行r(run命令的縮寫)執行程式,直至退出,換句話說,上述命令是一個完整的使用gdb運行可執行程式的完整程序(只使用了r命令),接下來,我們將以此為例子,介紹幾種比較常見的命令,

斷點
(gdb) b 15
Breakpoint 1 at 0x400601: file test_main.cc, line 15.
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400601 in main() at test_main.cc:15
(gdb) r
Starting program: /root/./test_main
In main():
   x is 10 and is stored at 0x7fffffffe424.
   ptr points to 0x7fffffffe424 which holds 10.

Breakpoint 1, main () at test_main.cc:15
15	  print(xx, xxptr);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64
(gdb)
backtrace
(gdb) backtrace
#0  main () at test_main.cc:15
(gdb)

backtrace命令是列出當前堆疊中的所有幀,在上面的例子中,堆疊上只有一幀,編號為0,屬于main函式,

(gdb) step
print (xx=10, xxptr=0x7fffffffe424) at test_main.cc:4
4	  printf("In print():\n");
(gdb)

接著,我們執行了step命令,即進入函式內,下面我們繼續通過backtrace命令來查看堆疊幀資訊,

(gdb) backtrace
#0  print (xx=10, xxptr=0x7fffffffe424) at test_main.cc:4
#1  0x0000000000400612 in main () at test_main.cc:15
(gdb)

從上面輸出結果,我們能夠看出,有兩個堆疊幀,第1幀屬于main函式,第0幀屬于print函式,

每個堆疊幀都列出了該函式的引數串列,從上面我們可以看出,main函式沒有引數,而print函式有引數,并且顯示了其引數的值,

有一點我們可能比較迷惑,在第一次執行backtrace的時候,main函式所在的堆疊幀編號為0,而第二次執行的時候,main函式的堆疊幀為1,而print函式的堆疊幀為0,這是因為_與堆疊的向下增長_規律一致,我們只需要記住_編號最小幀號就是最近一次呼叫的函式_,

frame

堆疊幀用來存盤函式的變數值等資訊,默認情況下,GDB總是位于當前正在執行函式對應堆疊幀的背景關系中,

在前面的例子中,由于當前正在print()函式中執行,GDB位于第0幀的背景關系中,可以通過frame命令來獲取當前正在執行的背景關系所在的幀

(gdb) frame
#0  print (xx=10, xxptr=0x7fffffffe424) at test_main.cc:4
4	  printf("In print():\n");
(gdb)

下面,我們嘗試使用print命令列印下當前堆疊幀的值,如下:

(gdb) print xx
$1 = 10
(gdb) print xxptr
$2 = (int *) 0x7fffffffe424
(gdb)

如果我們想看其他堆疊幀的內容呢?比如main函式中x和ptr的資訊呢?假如直接列印這倆值的話,那么就會得到如下:

(gdb) print x
No symbol "x" in current context.
(gdb) print xxptr
No symbol "ptr" in current context.
(gdb)

在此,我們可以通過_frame num_來切換堆疊幀,如下:

(gdb) frame 1
#1  0x0000000000400612 in main () at test_main.cc:15
15	  print(x, ptr);
(gdb) print x
$3 = 10
(gdb) print ptr
$4 = (int *) 0x7fffffffe424
(gdb)

多執行緒

為了方便進行演示,我們創建一個簡單的例子,代碼如下:

#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <vector>

int fun_int(int n) {
  std::this_thread::sleep_for(std::chrono::seconds(10));
  std::cout << "in fun_int n = " << n << std::endl;
  
  return 0;
}

int fun_string(const std::string &s) {
  std::this_thread::sleep_for(std::chrono::seconds(10));
  std::cout << "in fun_string s = " << s << std::endl;
  
  return 0;
}

int main() {
  std::vector<int> v;
  v.emplace_back(1);
  v.emplace_back(2);
  v.emplace_back(3);

  std::cout << v.size() << std::endl;

  std::thread t1(fun_int, 1);
  std::thread t2(fun_string, "test");

  std::cout << "after thread create" << std::endl;
  t1.join();
  t2.join();
  return 0;
}

上述代碼比較簡單:

  • 函式fun_int的功能是休眠10s,然后列印其引數
  • 函式fun_string功能是休眠10s,然后列印其引數
  • main函式中,創建兩個執行緒,分別執行上述兩個函式

下面是一個完整的除錯程序:

(gdb) b 27
Breakpoint 1 at 0x4013d5: file test.cc, line 27.
(gdb) b test.cc:32
Breakpoint 2 at 0x40142d: file test.cc, line 32.
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004013d5 in main() at test.cc:27
2       breakpoint     keep y   0x000000000040142d in main() at test.cc:32
(gdb) r
Starting program: /root/test
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, main () at test.cc:27
(gdb) c
Continuing.
3
[New Thread 0x7ffff6fd2700 (LWP 44996)]
in fun_int n = 1
[New Thread 0x7ffff67d1700 (LWP 44997)]

Breakpoint 2, main () at test.cc:32
32	  std::cout << "after thread create" << std::endl;
(gdb) info threads
  Id   Target Id         Frame
  3    Thread 0x7ffff67d1700 (LWP 44997) "test" 0x00007ffff7051fc3 in new_heap () from /lib64/libc.so.6
  2    Thread 0x7ffff6fd2700 (LWP 44996) "test" 0x00007ffff7097e2d in nanosleep () from /lib64/libc.so.6
* 1    Thread 0x7ffff7fe7740 (LWP 44987) "test" main () at test.cc:32
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff6fd2700 (LWP 44996))]
#0  0x00007ffff7097e2d in nanosleep () from /lib64/libc.so.6
(gdb) bt
#0  0x00007ffff7097e2d in nanosleep () from /lib64/libc.so.6
#1  0x00007ffff7097cc4 in sleep () from /lib64/libc.so.6
#2  0x00007ffff796ceb9 in std::this_thread::__sleep_for(std::chrono::duration<long, std::ratio<1l, 1l> >, std::chrono::duration<long, std::ratio<1l, 1000000000l> >) () from /lib64/libstdc++.so.6
#3  0x00000000004018cc in std::this_thread::sleep_for<long, std::ratio<1l, 1l> > (__rtime=...) at /usr/include/c++/4.8.2/thread:281
#4  0x0000000000401307 in fun_int (n=1) at test.cc:9
#5  0x0000000000404696 in std::_Bind_simple<int (*(int))(int)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x609080)
    at /usr/include/c++/4.8.2/functional:1732
#6  0x000000000040443d in std::_Bind_simple<int (*(int))(int)>::operator()() (this=0x609080) at /usr/include/c++/4.8.2/functional:1720
#7  0x000000000040436e in std::thread::_Impl<std::_Bind_simple<int (*(int))(int)> >::_M_run() (this=0x609068) at /usr/include/c++/4.8.2/thread:115
#8  0x00007ffff796d070 in ?? () from /lib64/libstdc++.so.6
#9  0x00007ffff7bc6dd5 in start_thread () from /lib64/libpthread.so.0
#10 0x00007ffff70d0ead in clone () from /lib64/libc.so.6
(gdb) c
Continuing.
after thread create
in fun_int n = 1
[Thread 0x7ffff6fd2700 (LWP 45234) exited]
in fun_string s = test
[Thread 0x7ffff67d1700 (LWP 45235) exited]
[Inferior 1 (process 45230) exited normally]
(gdb) q

在上述除錯程序中:

  1. b 27 在第27行加上斷點

  2. b test.cc:32 在第32行加上斷點(效果與b 32一致)

  3. info b 輸出所有的斷點資訊

  4. r 程式開始運行,并在第一個斷點處暫停

  5. c 執行c命令,在第二個斷點處暫停,在第一個斷點和第二個斷點之間,創建了兩個執行緒t1和t2

  6. info threads 輸出所有的執行緒資訊,從輸出上可以看出,總共有3個執行緒,分別為main執行緒、t1和t2

  7. thread 2 切換至執行緒2

  8. bt 輸出執行緒2的堆疊資訊

  9. c 直至程式結束

  10. q 退出gdb

多行程

同上面一樣,我們仍然以一個例子進行模擬多行程除錯,代碼如下:

#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid = fork();
    if (pid == -1) {
       perror("fork error\n");
       return -1;
    }
  
    if(pid == 0) { // 子行程
        int num = 1;
        while(num == 1){
          sleep(10);
         }
        printf("this is child,pid = %d\n", getpid());
    } else { // 父行程
        printf("this is parent,pid = %d\n", getpid());
      wait(NULL); // 等待子行程退出
    }
    return 0;
}

在上面代碼中,包含兩個行程,一個是父行程(也就是main行程),另外一個是由fork()函式創建的子行程,

在默認情況下,在多行程程式中,GDB只除錯main行程,也就是說無論程式呼叫了多少次fork()函式創建了多少個子行程,GDB在默認情況下,只除錯父行程,為了支持多行程除錯,從GDB版本7.0開始支持單獨除錯(除錯父行程或者子行程)和同時除錯多個行程,

那么,我們該如何除錯子行程呢?我們可以使用如下幾種方式進行子行程除錯,

attach

首先,無論是父行程還是子行程,都可以通過attach命令啟動gdb進行除錯,我們都知道,對于每個正在運行的程式,作業系統都會為其分配一個唯一ID號,也就是行程ID,如果我們知道了行程ID,就可以使用attach命令對其進行除錯了,

在上面代碼中,fork()函式創建的子行程內部,首先會進入while回圈sleep,然后在while回圈之后呼叫printf函式,這樣做的目的有如下:

  • 幫助attach捕獲要除錯的行程id
  • 在使用gdb進行除錯的時候,真正的代碼(即print函式)沒有被執行,這樣就可以從頭開始對子行程進行除錯

可能會有疑惑,上面代碼以及進入while回圈,無論如何是不會執行到下面printf函式,其實,這就是gdb的厲害之處,可以通過gdb命令修改num的值,以便其跳出while回圈

使用如下命令編譯生成可執行檔案test_process

g++ -g test_process.cc -o test_process

現在,我們開始嘗試啟動除錯,

gdb -q ./test_process
Reading symbols from /root/test_process...done.
(gdb)

這里需要說明下,之所以加-q選項,是想去掉其他不必要的輸出,q為quite的縮寫,

(gdb) r
Starting program: /root/./test_process
Detaching after fork from child process 37482.
this is parent,pid = 37478
[Inferior 1 (process 37478) exited normally]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64 libgcc-4.8.5-36.el7.x86_64 libstdc++-4.8.5-36.el7.x86_64
(gdb) attach 37482
//符號類輸出,此處略去
(gdb) n
Single stepping until exit from function __nanosleep_nocancel,
which has no line number information.
0x00007ffff72b3cc4 in sleep () from /lib64/libc.so.6
(gdb)
Single stepping until exit from function sleep,
which has no line number information.
main () at test_process.cc:8
8	      while(num==10){
(gdb)

在上述命令中,我們執行了n(next的縮寫),使其重新對while回圈的判斷體進行判斷,

(gdb) set num = 1
(gdb) n
12	      printf("this is child,pid = %d\n",getpid());
(gdb) c
Continuing.
this is child,pid = 37482
[Inferior 1 (process 37482) exited normally]
(gdb)

為了退出while回圈,我們使用set命令設定了num的值為1,這樣條件就會失效退出while回圈,進而執行下面的printf()函式;在最后我們執行了c(continue的縮寫)命令,支持程式退出,

如果程式正在正常運行,出現了死鎖等現象,則可以通過ps獲取行程ID,然后根據gdb attach pid進行系結,進而查看堆疊資訊

指定行程

默認情況下,GDB除錯多行程程式時候,只除錯父行程,GDB提供了兩個命令,可以通過follow-fork-mode和detach-on-fork來指定除錯父行程還是子行程,

follow-fork-mode

該命令的使用方式為:

(gdb) set follow-fork-mode mode

其中,mode有以下兩個選項:

  • parent:父行程,mode的默認選項
  • child:子行程,其目的是告訴 gdb 在目標應用呼叫fork之后接著除錯子行程而不是父行程,因為在Linux系統中fork()系統呼叫成功會回傳兩次,一次在父行程,一次在子行程
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) set follow-fork-mode child
(gdb) r
Starting program: /root/./test_process
[New process 37830]
this is parent,pid = 37826

^C
Program received signal SIGINT, Interrupt.
[Switching to process 37830]
0x00007ffff72b3e10 in __nanosleep_nocancel () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64 libgcc-4.8.5-36.el7.x86_64 libstdc++-4.8.5-36.el7.x86_64
(gdb) n
Single stepping until exit from function __nanosleep_nocancel,
which has no line number information.
0x00007ffff72b3cc4 in sleep () from /lib64/libc.so.6
(gdb) n
Single stepping until exit from function sleep,
which has no line number information.
main () at test_process.cc:8
8	      while(num==10){
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "child".
(gdb)

在上述命令中,我們做了如下操作:

  1. show follow-fork-mode:通過該命令來查看當前處于什么模式下,通過輸出可以看出,處于parent即父行程模式
  2. set follow-fork-mode child:指定除錯子行程模式
  3. r:運行程式,直接運行程式,此時會進入子行程,然后執行while回圈
  4. ctrl + c:通過該命令,可以使得GDB收到SIGINT命令,從而暫停執行while回圈
  5. n(next):繼續執行,進而進入到while回圈的條件判斷處
  6. show follow-fork-mode:再次執行該命令,通過輸出可以看出,當前處于child模式下
detach-on-fork

如果一開始指定要除錯子行程還是父行程,那么使用follow-fork-mode命令完全可以滿足需求;但是如果想在除錯程序中,想根據實際情況在父行程和子行程之間來回切換除錯呢?

GDB提供了另外一個命令:

(gdb) set detach-on-fork mode

其中mode有如下兩個值:

on:默認值,即表明只除錯一個行程,可以是子行程,也可以是父行程

off:程式中的每個行程都會被記錄,進而我們可以對所有的行程進行除錯

如果選擇關閉detach-on-fork模式(mode為off),那么GDB將保留對所有被fork出來的行程控制,即可用除錯所有被fork出來的行程,可用 使用info forks命令列出所有的可被GDB除錯的fork行程,并可用使用fork命令從一個fork行程切換到另一個fork行程,

  • info forks: 列印DGB控制下的所有被fork出來的行程串列,該串列包括fork id、行程id和當前行程的位置
  • fork fork-id: 引數fork-id是GDB分配的內部fork編號,該編號可用通過上面的命令info forks獲取

coredump

當我們開發或者使用一個程式時候,最怕的莫過于程式莫名其妙崩潰,為了分析崩潰產生的原因,作業系統的記憶體內容(包括程式崩潰時候的堆疊等資訊)會在程式崩潰的時候dump出來(默認情況下,這個檔案名為core.pid,其中pid為行程id),這個dump操作叫做coredump(核心轉儲),然后我們可以用除錯器除錯此檔案,以還原程式崩潰時候的場景,

在我們分析如果用gdb除錯coredump檔案之前,先需要生成一個coredump,為了簡單起見,我們就用如下例子來生成:

#include <stdio.h>

void print(int *v, int size) {
  for (int i = 0; i < size; ++i) {
    printf("elem[%d] = %d\n", i, v[i]);
  }
}

int main() {
  int v[] = {0, 1, 2, 3, 4};
  print(v, 1000);
  return 0;
}

編譯并運行該程式:

g++ -g test_core.cc -o test_core
./test_core

輸出如下:

elem[775] = 1702113070
elem[776] = 1667200115
elem[777] = 6648431
elem[778] = 0
elem[779] = 0
段錯誤(吐核)

如我們預期,程式產生了例外,但是卻沒有生成coredump檔案,這是因為在系統默認情況下,coredump生成是關閉的,所以需要設定對應的選項以打開coredump生成,

針對多執行緒程式產生的coredump,有時候其堆疊資訊并不能完整的去分析原因,這就使得我們得有其他方式,

18年有一次線上故障,在測驗環境一切正常,但是在線上的時候,就會coredump,根據gdb除錯coredump,只能定位到了libcurl里面,但卻定位不出原因,用了大概兩天的時間,發現只有在超時的時候,才會coredump,而測驗環境因為配置比較差超時設定的是20ms,而線上是5ms,知道coredump原因后,采用逐步定位縮小范圍法,逐步縮小代碼范圍,最終定位到是libcurl一個bug導致,所以,很多時候,定位線上問題需要結合實際情況,采取合適的方法來定位問題,

配置

配置coredump生成,有臨時配置(退出終端后,配置失效)和永久配置兩種,

臨時

通過ulimit -a可以判斷當前有沒有配置coredump生成:

ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0

從上面輸出可以看出core file size后面的數為0,即不生成coredump檔案,我們可以通過如下命令進行設定

ulimit -c size

其中size為允許生成的coredump大小,這個一般盡量設定大點,以防止生成的coredump資訊不全,筆者一般設定為不限,

ulimit -c unlimited

需要說明的是,臨時配置的coredump選項,其默認生成路徑為執行該命令時候的路徑,可以通過修改配置來進行路徑修改,

永久

上面的設定只是使能了core dump功能,預設情況下,內核在coredump時所產生的core檔案放在與該程式相同的目錄中,并且檔案名固定為core,很顯然,如果有多個程式產生core檔案,或者同一個程式多次崩潰,就會重復覆寫同一個core檔案,

過修改kernel的引數,可以指定內核所生成的coredump檔案的檔案名,使用下面命令,可以實作coredump永久配置、存放路徑以及生成coredump名稱等,

mkdir -p /www/coredump/
chmod 777 /www/coredump/

/etc/profile
ulimit -c unlimited

/etc/security/limits.conf
*          soft     core   unlimited

echo "/www/coredump/core-%e-%p-%h-%t" > /proc/sys/kernel/core_pattern
除錯

現在,我們重新執行如下命令,按照預期產生coredump檔案:

./test_coredump

elem[955] = 1702113070
elem[956] = 1667200115
elem[957] = 6648431
elem[958] = 0
elem[959] = 0
段錯誤(吐核)

然后使用下面的命令進行coredump除錯:

gdb ./test_core -c /www/coredump/core_test_core_1640765384_38924 -q

輸出如下:

#0  0x0000000000400569 in print (v=0x7fff3293c100, size=1000) at test_core.cc:5
5	    printf("elem[%d] = %d\n", i, v[i]);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64 libgcc-4.8.5-36.el7.x86_64 libstdc++-4.8.5-36.el7.x86_64
(gdb)

可以看出,程式core在了第5行,此時,我們可以通過where命令來查看堆疊回溯資訊,

在gdb中輸入where命令,可以獲取堆疊呼叫資訊,當進行coredump除錯時候,這個是最基本且最有用處的命令,where命令輸出的結果包含程式中 的函式名稱和相關引數值,

通過where命令,我們能夠發現程式core在了第5行,那么根據分析原始碼基本就能定位原因,

需要注意的是,在多執行緒運行的時候,core不一定在當前執行緒,這就需要我們對代碼有一定的了解,能夠保證哪塊代碼是安全的,然后通過thread num切換執行緒,然后再通過bt或者where命令查看堆疊資訊,進而定位coredump原因,

原理

在前面幾節,我們講了gdb的命令,以及這些命令在除錯時候的作用,并以例子進行了演示,作為C/C++ coder,要知其然,更要知其所以然,所以,借助本節,我們大概講下GDB除錯的原理,

gdb 通過系統呼叫 ptrace 來接管一個行程的執行,ptrace 系統呼叫提供了一種方法使得父行程可以觀察和控制其它行程的執行,檢查和改變其核心映像以及暫存器,它主要用來實作斷點除錯和系統呼叫跟蹤,

ptrace系統呼叫定義如下:

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data)
  • pid_t pid:指示 ptrace 要跟蹤的行程
  • void *addr:指示要監控的記憶體地址
  • enum __ptrace_request request:決定了系統呼叫的功能,幾個主要的選項:
    • PTRACE_TRACEME:表示此行程將被父行程跟蹤,任何信號(除了 SIGKILL)都會暫停子行程,接著阻塞于 wait() 等待的父行程被喚醒,子行程內部對 exec() 的呼叫將發出 SIGTRAP 信號,這可以讓父行程在子行程新程式開始運行之前就完全控制它
    • PTRACE_ATTACH:attach 到一個指定的行程,使其成為當前行程跟蹤的子行程,而子行程的行為等同于它進行了一次 PTRACE_TRACEME 操作,但需要注意的是,雖然當前行程成為被跟蹤行程的父行程,但是子行程使用 getppid() 的到的仍將是其原始父行程的pid
    • PTRACE_CONT:繼續運行之前停止的子行程,可同時向子行程交付指定的信號

除錯原理

運行并除錯新行程

運行并除錯新行程,步驟如下:

  • 運行gdb exe
  • 輸入run命令,gdb執行以下操作:
    • 通過fork()系統呼叫創建一個新行程
    • 在新創建的子行程中執行ptrace(PTRACE_TRACEME, 0, 0, 0)操作
    • 在子行程中通過execv()系統呼叫加載指定的可執行檔案

attach運行的行程

可以通過gdb attach pid來除錯一個運行的行程,gdb將對指定行程執行ptrace(PTRACE_ATTACH, pid, 0, 0)操作,

需要注意的是,當我們attach一個行程id時候,可能會報如下錯誤:

Attaching to process 28849
ptrace: Operation not permitted.

這是因為沒有權限進行操作,可以根據啟動該行程用戶下或者root下進行操作,

斷點原理

實作原理

當我們通過b或者break設定斷點時候,就是在指定位置插入斷點指令,當被除錯的程式運行到斷點的時候,產生SIGTRAP信號,該信號被gdb捕獲并 進行斷點命中判斷,

設定原理

在程式中設定斷點,就是先在該位置保存原指令,然后在該位置寫入int 3,當執行到int 3時,發生軟中斷,內核會向子行程發送SIGTRAP信號,當然,這個信號會轉發給父行程,然后用保存的指令替換int 3并等待操作恢復,

命中判斷

gdb將所有斷點位置存盤在一個鏈表中,命中判定將被除錯程式的當前停止位置與鏈表中的斷點位置進行比較,以查看斷點產生的信號,

條件判斷

在斷點處恢復指令后,增加了一個條件判斷,如果運算式為真,則觸發斷點,由于需要判斷一次,添加條件斷點后,是否觸發條件斷點,都會影響性能,在 x86 平臺上,部分硬體支持硬體斷點,不是在條件斷點處插入 int 3,而是插入另一條指令,當程式到達這個地址時,不是發出int 3信號,而是進行比較,特定暫存器的內容和某個地址,然后決定是否發送int 3,因此,當你的斷點位置被程式頻繁“通過”時,盡量使用硬體斷點,這將有助于提高性能,

單步原理

這個ptrace函式本身就支持,可以通過ptrace(PTRACE_SINGLESTEP, pid,…)呼叫來實作單步,

 printf("attaching to PID %d\n", pid);
    if (ptrace(PTRACE_ATTACH, pid, 0, 0) != 0)
    {
        perror("attach failed");
    }
    int waitStat = 0;
    int waitRes = waitpid(pid, &waitStat, WUNTRACED);
    if (waitRes != pid || !WIFSTOPPED(waitStat))
    {
        printf("unexpected waitpid result!\n");
        exit(1);
    }
   
    int64_t numSteps = 0;
    while (true) {
        auto res = ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
    }

上述代碼,首先接收一個pid,然后對其進行attach,最后呼叫ptrace進行單步除錯,

其它

借助本文,簡單介紹下筆者作業程序中使用的一些其他命令或者工具,

pstack

此命令可顯示每個行程的堆疊跟蹤,pstack 命令必須由相應行程的屬主或 root 運行,可以使用 pstack 來確定行程掛起的位置,此命令允許使用的唯一選項是要檢查的行程的 PID,

這個命令在排查行程問題時非常有用,比如我們發現一個服務一直處于work狀態(如假死狀態,好似死回圈),使用這個命令就能輕松定位問題所在;可以在一段時間內,多執行幾次pstack,若發現代碼堆疊總是停在同一個位置,那個位置就需要重點關注,很可能就是出問題的地方;

以前面的多執行緒代碼為例,其行程ID是4507(在筆者本地),那么通過

pstack 4507輸出結果如下:

Thread 3 (Thread 0x7f07aaa69700 (LWP 45708)):
#0  0x00007f07aab2ee2d in nanosleep () from /lib64/libc.so.6
#1  0x00007f07aab2ecc4 in sleep () from /lib64/libc.so.6
#2  0x00007f07ab403eb9 in std::this_thread::__sleep_for(std::chrono::duration<long, std::ratio<1l, 1l> >, std::chrono::duration<long, std::ratio<1l, 1000000000l> >) () from /lib64/libstdc++.so.6
#3  0x00000000004018cc in void std::this_thread::sleep_for<long, std::ratio<1l, 1l> >(std::chrono::duration<long, std::ratio<1l, 1l> > const&) ()
#4  0x00000000004012de in fun_int(int) ()
#5  0x0000000000404696 in int std::_Bind_simple<int (*(int))(int)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) ()
#6  0x000000000040443d in std::_Bind_simple<int (*(int))(int)>::operator()() ()
#7  0x000000000040436e in std::thread::_Impl<std::_Bind_simple<int (*(int))(int)> >::_M_run() ()
#8  0x00007f07ab404070 in ?? () from /lib64/libstdc++.so.6
#9  0x00007f07ab65ddd5 in start_thread () from /lib64/libpthread.so.0
#10 0x00007f07aab67ead in clone () from /lib64/libc.so.6
Thread 2 (Thread 0x7f07aa268700 (LWP 45709)):
#0  0x00007f07aab2ee2d in nanosleep () from /lib64/libc.so.6
#1  0x00007f07aab2ecc4 in sleep () from /lib64/libc.so.6
#2  0x00007f07ab403eb9 in std::this_thread::__sleep_for(std::chrono::duration<long, std::ratio<1l, 1l> >, std::chrono::duration<long, std::ratio<1l, 1000000000l> >) () from /lib64/libstdc++.so.6
#3  0x00000000004018cc in void std::this_thread::sleep_for<long, std::ratio<1l, 1l> >(std::chrono::duration<long, std::ratio<1l, 1l> > const&) ()
#4  0x0000000000401340 in fun_string(std::string const&) ()
#5  0x000000000040459f in int std::_Bind_simple<int (*(char const*))(std::string const&)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) ()
#6  0x000000000040441f in std::_Bind_simple<int (*(char const*))(std::string const&)>::operator()() ()
#7  0x0000000000404350 in std::thread::_Impl<std::_Bind_simple<int (*(char const*))(std::string const&)> >::_M_run() ()
#8  0x00007f07ab404070 in ?? () from /lib64/libstdc++.so.6
#9  0x00007f07ab65ddd5 in start_thread () from /lib64/libpthread.so.0
#10 0x00007f07aab67ead in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x7f07aba80740 (LWP 45707)):
#0  0x00007f07ab65ef47 in pthread_join () from /lib64/libpthread.so.0
#1  0x00007f07ab403e37 in std::thread::join() () from /lib64/libstdc++.so.6
#2  0x0000000000401455 in main ()

在上述輸出結果中,將行程內部的詳細資訊都輸出在終端,以方便分析問題,

ldd

在我們編譯程序中通常會提示編譯失敗,通過輸出錯誤資訊發現是找不到函式定義,再或者編譯成功了,但是運行時候失敗(往往是因為依賴了非正常版本的lib庫導致),這個時候,我們就可以通過ldd來分析該可執行檔案依賴了哪些庫以及這些庫所在的路徑,

用來查看程式運行所需的共享庫,常用來解決程式因缺少某個庫檔案而不能運行的一些問題,

仍然查看可執行程式test_thread的依賴庫,輸出如下:

ldd -r ./test_thread
	linux-vdso.so.1 =>  (0x00007ffde43bc000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f8c5e310000)
	libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f8c5e009000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f8c5dd07000)
	libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f8c5daf1000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f8c5d724000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f8c5e52c000)

在上述輸出中:

  • 第一列:程式需要依賴什么庫
  • 第二列:系統提供的與程式需要的庫所對應的庫
  • 第三列:庫加載的開始地址

在有時候,我們通過ldd查看依賴庫的時候,會提示找不到庫,如下:

ldd -r test_process
	linux-vdso.so.1 =>  (0x00007ffc71b80000)
	libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fe4badd5000)
	libm.so.6 => /lib64/libm.so.6 (0x00007fe4baad3000)
	libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fe4ba8bd000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fe4ba4f0000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fe4bb0dc000)
	 liba.so => not found

比如上面最后一句提示,liba.so找不到,這個時候,需要我們知道liba.so的路徑,比如在/path/to/liba.so,那么可以有下面兩種方式:

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/

這樣在通過ldd查看,就能找到對應的lib庫,但是這個缺點是臨時的,即退出終端后,再執行ldd,仍然會提示找不到該庫,所以就有了另外一種方式,即通過修改/etc/ld.so.conf,在該檔案的后面加上需要的路徑,即

include ld.so.conf.d/*.conf
/path/to/

然后通過如下命令,即可永久生效

 /sbin/ldconfig

c++filter

因為c++支持多載,也就引出了編譯器的name mangling機制,對函式進行重命名,

我們通過strings命令查看test_thread中的函式資訊(僅輸出fun等相關)

strings test_thread | grep fun_
in fun_int n =
in fun_string s =
_GLOBAL__sub_I__Z7fun_inti
_Z10fun_stringRKSs

可以看到_Z10fun_stringRKSs這個函式,如果想知道這個函式定義的話,可以使用c++filt命令,如下:

 c++filt _Z10fun_stringRKSs
fun_string(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)

通過上述輸出,我們可以將編譯器生成的函式名還原到我們代碼中的函式名即fun_string,

結語

GDB是一個在Linux上進行開發的一個必不可少的除錯工具,使用場景依賴于具體的需求或者遇到的具體問題,在我們的日常開發作業中,熟練使用GDB加以輔助,能夠使得開發程序事半功倍,

本文從一些簡單的命令出發,通過舉例除錯可執行程式(單執行緒、多執行緒以及多行程場景)、coredump檔案等各個場景,使得大家能夠更加直觀的了解GDB的使用,GDB功能非常強大,筆者作業中使用的都是非常基本的一些功能,如果想深入理解GDB,則需要去官網進行閱讀了解,

本文從構思到完成,大概用了三周時間,寫作程序是痛苦的(需要整理資料以及構建各種場景,以及將各種現場還原),同時又是識訓滿滿的,通過本文,進一步加深了對GDB的底層原理理解,

參考

(https://www.codetd.com/en/article/13107993)

https://users.ece.utexas.edu/~adnan/gdb-refcard.pdf
https://www.cloudsavvyit.com/10921/debugging-with-gdb-getting-started/
https://blog.birost.com/a?ID=00650-b03e2257-94bf-41f3-b0fc-d352d5b02431
https://www.cnblogs.com/xsln/p/ptrace.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/404133.html

標籤:其他

上一篇:分分鐘帶你解決資料結構問題---- List介面中的ArrayList

下一篇:LeetCode -2074 -反轉偶數長度組的節點 - java - 細喔

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more