天天看點

NULL 指針在不同平台下的表現引發程式報錯(C 語言)

NULL 指針在不同平台下的表現引發程式報錯(C 語言)

        • 為什麼有這篇
        • 正文
        • 1. 使用環境
        • 2.問題描述及展示
        • 3. 走了些彎路
        • 4. 柳岸花明(GDB 調試)
        • 5. 通路 NULL 指針錯誤背後的原理
        • 總結

 作者:高玉涵

 時間:2022.6.1 13:47

 部落格:blog.csdn.net/cg_i

眼前的高山多少讓我有點膽顫。—— 2021.5.17 微信朋友圈

為什麼有這篇

 下面這段話引用自 2021 年 5 月 17 日,我首次接到任務時寫的一篇日記《X86 項目移植的思考》開篇的部分内容:

 把一個運作在某個作業系統和硬體結構上的軟體程式,在另一個作業系統和硬體結構上重新編譯(包括一些必要的修改),以便在新的平台上運作,這一過程叫做應用程式移植。有些情況下,把應用程式從一個平台移植到另一個平台上非常簡單直接,僅需要重新編譯并進行一些驗證測試即可。但是有些情況下,移植程式并不是這麼容易。一些大的應用程式可能是在較老的作業系統上編寫的,并且是用作業系統特有的編譯器編譯的,這些應用程式可能并不遵循現在的程式設計語言标準,是以移植起來就比較困難 …

 時隔一年,随着時間推移,我對此項工作的了解也逐漸加深。邊學、邊幹的過程中,項目正按時有條不紊的推進中,離預期目标也越來越近(期間感謝這裡就一一不表了)。可能緣于“二八法則”越深入,真正的“硬骨頭”才顯現出來。回望以往寫過的這段話,再結合這周的工作經曆,當時擔心的事終還是發生了,而且傷害來得極快。

 如标題所述,我下面給出的例子是真實存在于項目中的(示例程式隻為說明問題做了簡化),并将排查、分析過程分享出來。一來,是為了說明遇到此類問題時排查的難度。二來,是為了給各位程式員們傳遞日常書寫程式時,遵循”标準“的重要性(這裡指的是 ANSI C 标準)。特别是 C 語言這種極度靈活,這有助于提高它的效率,但也增加了出錯的可能性。例如,C 對數組下标引用和指針通路并不進行有效性檢查,這可以節省時間(相信我,你節省的那點時間,必将成倍反噬于你),但你在使用這些特性時就必須特别小心。如果你在使用 C 語言時能夠遵守相關規定,就可以避免這些潛在的問題。

 最後,希望此文能有助于您,避開可能遇到的痛苦。

正文

1. 使用環境

  • CPU

 HPUNIX:

安騰 9700 系列

 LINUX:

X86 架構

  • 作業系統

 HPUNIX:

HP-UX xxxxx2 B.11.31 U ia64 2932500072 unlimited-user license

 LINUX:

Linux xxxxkf1 4.19.90-24.4.v2101.ky10.x86_64 #1 SMP Mon May 24 12:14:55 CST 2021 x86_64 x86_64 x86_64 GNU/Linux

  • 編譯器程式及版本

 HPUNIX:

cc: HP C/aC++ B3910B A.06.25 [Nov 30 2009]

 LINUX:

使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-linux-gnu/7.3.0/lto-wrapper
目标:x86_64-linux-gnu
配置為:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,fortran,lto --enable-plugin --enable-initfini-array --disable-libgcj --without-isl --without-cloog --enable-gnu-indirect-function --build=x86_64-linux-gnu --with-stage1-ldflags=' -Wl,-z,relro,-z,now' --with-boot-ldflags=' -Wl,-z,relro,-z,now' --with-tune=generic --with-arch_32=x86-64 --disable-multilib
線程模型:posix
gcc 版本 7.3.0 (GCC) 
           

2.問題描述及展示

 先看示例代碼:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int BDPceshi( char *sDPACNO, char *sFRENUM, double *dFREAMT )
{
    memset(sDPACNO,0,sizeof(sDPACNO));
    printf("!!!!!!!!!\n");

    sprintf(sDPACNO,"%s",sFRENUM);
    printf("!!!!!!!!![%s]\n",sDPACNO);

    sprintf(sDPACNO,"%s",sFRENUM);

    if( 0 == strlen( sFRENUM ) )
    {
    	printf("會core掉,應該列印不出來\n");
    }
    return 0;
}
int main()
{
    double dFREAMT;
    char sDPACNO[254];
    int iRe= BDPceshi(sDPACNO,NULL,&dFREAMT);
    if(iRe != 0)
    {
    	printf("列印成功\n");
    }
}
           
  • LINUX 編譯并運作
gcc -o t t.c
#./t
!!!!!!!!!
段錯誤 (核心已轉儲)
           
  • HPUX 編譯并運作
cc -o t t.c
./t
!!!!!!!!!
!!!!!!!!![]
會core掉,應該列印不出來
           

 如你所見代碼在兩個平台下,編譯通過程式生成成功。但在運作時 LINUX 下會崩潰,HPUNIX 下正常(起碼看起來像)。運作時錯誤的排查難點:編譯錯誤可由編譯器檢查出來,而多數編譯器對運作時錯誤卻無能為力,查錯和糾錯隻能由人工完成,考慮到存量代碼已積累多年,此項工作繁重。讓我們先分析一下代碼,在 main 函數裡唯一做的就是調用了一個名為

BDPceshi

的函數,這個函數内部也不神秘,無非是初始化和格式化字元串并輸出的操作,看似也沒什麼問題。讓我們回過頭再仔細觀察, main 函數在調用

BDPceshi

時,根據

int BDPceshi( char *sDPACNO, char *sFRENUM, double *dFREAMT )

函數定義,它接收 3 個參數,前 2 個是指向字元型的指針,最後 1 個是雙精度浮點型數值。調用時,在第 2 個參數所在位置,卻給它傳遞了一個 NULL 值

BDPceshi(sDPACNO,NULL,&dFREAMT);

這裡發生了參數資料類型調用和定義時不一緻的情景。即使是這樣,當時我也并沒覺得有什麼了不起,在 C 語言裡這種情況太稀松平常了,不然也不會有預設資料類型轉換這種東西的存在。

 話雖如此,初步分析問題可能出在這個地方。那麼NULL 指針在不同平台下的表現如何?帶着凝問我開始了下面的排查。

3. 走了些彎路

  1. 擷取編譯器預設執行标準

 我首先想到兩個平台的 C 編譯器,預設執行的“标準”可能不同,生成的機器碼自然也不同。首先,我得先弄清楚們各自差別。擷取編譯器預設執行标準代碼如下:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("%d\n", __STDC__);
    printf("%ld\n",__STDC_VERSION__);
    return 0;
}
           
  • LINUX
gcc -o std std.c
./std
1
201112
           
  • HPUX
cc -o std std.c
./std 
1
199901
           

 根據輸出結果獲知 LINUX、HPUX 編譯器,各自預設執行的标準是 C11 和 C99。

  1. 擷取不同編譯器生成的彙編碼
  • HPUX
.file	"t.c"
	.psr	msb
	.radix	C
	.pred.safe_across_calls p1-p5,p16-p63
	.allow 0, "ao", "x1", "x2", "da"
	.type	sprintf,@function
	.global	sprintf
	.type	memset,@function
	.global	memset
	.type	BDPceshi,@function
	.global	BDPceshi
	.type	main,@function
	.global	main
	.type	_memset,@function
	.global	_memset
	.size	BDPceshi, 320
// Routine [id=0004] ( BDPceshi )

// ===
	.secalias .abe$3.text, ".text"
	.section .abe$3.text = "ax", "progbits"
	.align	16
	.proc	BDPceshi
..L0:
//      $start          ARid768 =               ;; // A

..L2:
BDPceshi::
.prologue
//      $entry          ARid770, S:r32, S:r33, S:r34 =  // A [t.c: 6/1]
//file/line/col t.c/6/1
.save ar.pfs, r40
        alloc           r40 = ar.pfs, 3, 7, 3, 0   // M [t.c: 6/1]
// SP
        add             sp = -512, sp              // M [t.c: 6/1]
.save rp, r41
        mov             r41 = rp                   // I [t.c: 6/1]
        add             r39 = 0, gp                // M
.body

        add             r37 = 0, r34               // M [t.c: 6/1]
        add             r36 = 0, r33            ;; // I [t.c: 6/1]
        add             r35 = 0, r32               // M [t.c: 6/1]
//file/line/col t.c/9/2
        add             out2 = 512, r0             // M [t.c: 9/2]
        add             out1 = 0, r0               // I [t.c: 9/2]
        add             r9 = 16, sp                // M [t.c: 9/2]
        add             r11 = 16, sp               // M [t.c: 9/2]
        add             r14 = r0, gp            ;; // I [t.c: 9/2]
        lfetch.nt1      [r9]                       // M [t.c: 9/2]
        add             r9 = @pltoff(_memset), r0  // I [t.c: 9/2]
        add             out0 = 0, r11           ;; // I [t.c: 9/2]
        add             r10 = r9, gp            ;; // M [t.c: 9/2]
        ld8.acq         r9 = [r10]                 // M [t.c: 9/2]
        add             r10 = 8, r10            ;; // I [t.c: 9/2]
        ld8             gp = [r10]                 // M [t.c: 9/2]
        mov             b6 = r9                    // I [t.c: 9/2]
        br.call.dptk.many rp = b6               ;; // B [t.c: 9/2] [FN: _memset#]
        add             gp = 0, r39                // M [t.c: 9/2]
//file/line/col t.c/10/2
        add             r9 = @ltoffx(.abe$2.rodata), r0 // M [t.c: 10/2]
        add             r11 = 16, sp               // I [t.c: 10/2]
        add             out2 = 0, r36              // M [t.c: 10/2]
        add             r10 = @pltoff(sprintf), r0 ;; // I [t.c: 10/2]
        add             r9 = r9, gp                // I [t.c: 10/2]
        add             r10 = r10, gp              // M [t.c: 10/2]
        add             r14 = r0, gp               // M [t.c: 10/2]
        add             out0 = 0, r11           ;; // I [t.c: 10/2]
        ldxmov          r38 = [r9], .abe$2.rodata# // M [t.c: 10/2]
        ld8.acq         r9 = [r10]                 // M [t.c: 10/2]
        add             r10 = 8, r10            ;; // I [t.c: 10/2]
        ld8             gp = [r10]                 // M [t.c: 10/2]
        add             out1 = 0, r38              // M [t.c: 10/2]
        mov             b6 = r9                    // I [t.c: 10/2]
        nop.m           0                          // M
        nop.m           0                          // M
        br.call.dptk.many rp = b6               ;; // B [t.c: 10/2] [FN: sprintf#]
        add             gp = 0, r39                // M [t.c: 10/2]
//file/line/col t.c/11/2
        add             r11 = 16, sp               // M [t.c: 11/2]
        add             out1 = 0, r38              // I [t.c: 11/2]
        add             out2 = 0, r36              // M [t.c: 11/2]
        add             r9 = @pltoff(sprintf), r0 ;; // I [t.c: 11/2]
        add             r10 = r9, gp               // I [t.c: 11/2]
        add             r14 = r0, gp               // M [t.c: 11/2]
        add             out0 = 0, r11           ;; // I [t.c: 11/2]
        nop.i           0                          // I
        ld8.acq         r9 = [r10]                 // M [t.c: 11/2]
        add             r10 = 8, r10            ;; // I [t.c: 11/2]
        mov             b6 = r9                    // I [t.c: 11/2]
        ld8             gp = [r10]                 // M [t.c: 11/2]
        nop.m           0                          // M
        br.call.dptk.many rp = b6               ;; // B [t.c: 11/2] [FN: sprintf#]
        add             gp = 0, r39                // M [t.c: 11/2]
//file/line/col t.c/12/2
        add             r8 = 0, r0                 // M [t.c: 12/2]
        mov             rp = r41                   // I [t.c: 12/2]
.restore sp
        add             sp = 512, sp               // M [t.c: 12/2]
        mov             ar.pfs = r40               // I [t.c: 12/2]
        br.ret.dptk.many rp                     ;; // B [t.c: 12/2]

..L1:
//      $end                                    ;; // A

	.endp	BDPceshi

// ===


// ===
	.secalias .abe$4.IA_64.unwind, ".IA_64.unwind"
	.section .abe$4.IA_64.unwind = "a", "unwind"
	.align 4
	data4.ua @segrel(.abe$3.text)
	data4.ua @segrel(.abe$3.text+320)
	data4.ua @segrel(.abe$unwind_info_block00000)

// ===
	.secalias .abe$5.IA_64.unwind_info, ".IA_64.unwind_info"
	.section .abe$5.IA_64.unwind_info = "a", "progbits"
	.align 4
// unwind_info_block: notype local temp
	data4.ua @segrel(.llo$annot_info_block0000)
.abe$unwind_info_block00000:	data1	0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04
	data1	0x04, 0xe4, 0x02, 0xb0, 0xa9, 0xe6, 0x00, 0xb1
	data1	0x28, 0xe0, 0x01, 0x20, 0x61, 0x38, 0xc0, 0x02
	.size	main, 80
// Routine [id=0007] ( main )

// ===
	.secalias .abe$7.text, ".text"
	.section .abe$7.text = "ax", "progbits"
	.align	16
	.proc	main
..L0:
//      $start          ARid768 =               ;; // A

..L2:
main::
.prologue
//      $entry          ARid770 =                  // A [t.c: 16/1]
//file/line/col t.c/16/1
.save ar.pfs, r33
        alloc           r33 = ar.pfs, 0, 3, 3, 0   // M [t.c: 16/1]
.save rp, r34
        mov             r34 = rp                   // I [t.c: 16/1]
// SP
        add             sp = -272, sp              // I [t.c: 16/1]
.body

//file/line/col t.c/19/9
        add             out1 = 0, r0            ;; // M [t.c: 19/9]
        add             r10 = 32, sp               // M [t.c: 19/9]
        add             r9 = 16, sp             ;; // I [t.c: 19/9]
        add             out0 = 0, r10              // M [t.c: 19/9]
        add             out2 = 0, r9               // M [t.c: 19/9]
        br.call.dptk.many rp = BDPceshi#        ;; // B [t.c: 19/9]
        add             r32 = 0, r8                // M [t.c: 19/9]
//file/line/col t.c/20/1
        add             r8 = 0, r0                 // M [t.c: 20/1]
        mov             rp = r34                   // I [t.c: 20/1]
.restore sp
        add             sp = 272, sp               // M [t.c: 20/1]
        mov             ar.pfs = r33               // I [t.c: 20/1]
        br.ret.dptk.many rp                     ;; // B [t.c: 20/1]

..L1:
//      $end                                    ;; // A

	.endp	main

// ===


// ===
	.secalias .abe$8.IA_64.unwind, ".IA_64.unwind"
	.section .abe$8.IA_64.unwind = "a", "unwind"
	.align 4
	data4.ua @segrel(.abe$7.text)
	data4.ua @segrel(.abe$7.text+80)
	data4.ua @segrel(.abe$unwind_info_block00001)

// ===
	.secalias .abe$9.IA_64.unwind_info, ".IA_64.unwind_info"
	.section .abe$9.IA_64.unwind_info = "a", "progbits"
	.align 4
// unwind_info_block: notype local temp
	data4.ua @segrel(.llo$annot_info_block0001)
.abe$unwind_info_block00001:	data1	0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04
	data1	0x03, 0xe4, 0x01, 0xb0, 0xa2, 0xe6, 0x00, 0xb1
	data1	0x21, 0xe0, 0x02, 0x11, 0x2c, 0xc0, 0x02, 0x00

// ===
	.secalias .abe$2.rodata, ".rodata"
	.section .abe$2.rodata = "a", "progbits"
	.align 8
// .rodata: section local
// _noname: notype local temp

.abe$_noname00002:	stringz	"%s"

// ===
	.secalias .abe$6.HP.opt_annot, ".HP.opt_annot"
	.section .abe$6.HP.opt_annot = "a", "annot"
	.align 8
// .llo$annot_info_block0000: object local temp
// .llo$annot_info_block0001: object local temp
.llo$annot_info_block0000:	data1	0x00, 0xac, 0x01, 0xbe, 0xff, 0xfe, 0xff, 0xff
	data1	0xff, 0xfc, 0xff, 0xff, 0xa0, 0xfc, 0x01, 0x02
	data1	0x84, 0x00, 0x02, 0x84, 0x03, 0x01, 0x84, 0x04
	data1	0x01, 0x03, 0x88, 0x02, 0x00, 0x1f, 0x00, 0x00
.llo$annot_info_block0001:	data1	0x00, 0xac, 0x01, 0xfe, 0xff, 0xfe, 0xff, 0xff
	data1	0xff, 0xfc, 0xff, 0xff, 0xe8, 0xfe, 0x01, 0x02
	data1	0x84, 0x00, 0x02, 0x84, 0x03, 0x01, 0x84, 0x04
	data1	0x01, 0x03, 0x88, 0x02, 0x00, 0x1f, 0x00

// ===
	.secalias .abe$10.debug_procs_info, ".debug_procs_info"
	.section .abe$10.debug_procs_info = "", "progbits"
	.align 1
	data1	0x00, 0x00, 0x00, 0x4b, 0x00, 0x02
	data4.ua @secrel(.abe$11.debug_procs_abbrev)
	data1	0x04, 0x03
	data4.ua @secrel(.abe$0.debug_line)
	data4.ua @secrel(.abe$1.debug_actual)
	stringz	"t.c"
	data1	0x02, 0x01, 0x47, 0x00
	stringz	"/root"
	data1	0x00, 0x00, 0x00, 0x00
	stringz	"\tt.c"
	data1	0x00, 0x00, 0x00, 0x30, 0x00, 0x07, 0x01, 0x00
	data1	0x00, 0x00, 0x00
	data4.ua .abe$3.text
	data4.ua .abe$3.text+320
	data1	0x00, 0x07, 0x01, 0x00, 0x00, 0x00, 0x00
	data4.ua .abe$7.text
	data4.ua .abe$7.text+80
	data1	0x00, 0x00

// ===
	.secalias .abe$11.debug_procs_abbrev, ".debug_procs_abbrev"
	.section .abe$11.debug_procs_abbrev = "", "progbits"
	.align 1
// .debug_procs_abbrev: section local
	data1	0x03, 0x11, 0x01, 0x10, 0x06, 0x90, 0x40, 0x06
	data1	0x03, 0x08, 0x13, 0x0b, 0x95, 0x40, 0x0b, 0x25
	data1	0x08, 0x1b, 0x08, 0x01, 0x13, 0x00, 0x00, 0x07
	data1	0x2e, 0x01, 0x94, 0x40, 0x0b, 0x96, 0x40, 0x06
	data1	0x11, 0x01, 0x12, 0x01, 0x00, 0x00, 0x09, 0x1e
	data1	0x01, 0x03, 0x08, 0x01, 0x13, 0x00, 0x00, 0x00

// ===
	.secalias .abe$0.debug_line, ".debug_line"
	.section .abe$0.debug_line = "", "progbits"
	.align 1
// .debug_line: section local
	data1	0x00, 0x00, 0x00, 0x4a, 0x00, 0x02, 0x00, 0x00
	data1	0x00, 0x10, 0x04, 0x01, 0x00, 0x05, 0x0a, 0x00
	data1	0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01
	data1	0x00, 0x00, 0x00, 0x08, 0x03
	stringz	"t.c"
	data1	0x00, 0x00, 0xdd, 0x02, 0x00, 0x05, 0x02
	data4.ua .abe$3.text
	data1	0x04, 0x01, 0x05, 0x01, 0x03, 0x05, 0x01, 0x05
	data1	0x02, 0x3a, 0x6f, 0x83, 0x6f, 0x00, 0x05, 0x02
	data4.ua .abe$7.text
	data1	0x05, 0x01, 0x0e, 0x05, 0x09, 0x21, 0x05, 0x01
	data1	0x38, 0x02, 0x04, 0x00, 0x01, 0x01

// ===
	.secalias .abe$1.debug_actual, ".debug_actual"
	.section .abe$1.debug_actual = "", "progbits"
	.align 1
// .debug_actual: section local
	data1	0x00, 0x00, 0x00, 0x69, 0x00, 0x02, 0x00, 0x00
	data1	0x00, 0x0e, 0x04, 0x01, 0x00, 0x05, 0x0a, 0x00
	data1	0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01
	data1	0x00, 0x05, 0x02
	data4.ua .abe$3.text
	data1	0x00, 0x01, 0x11, 0x0b, 0x00, 0x01, 0x11, 0x0f
	data1	0x00, 0x01, 0x11, 0x1e, 0x00, 0x01, 0x11, 0x0f
	data1	0x00, 0x01, 0x11, 0x14, 0x10, 0x6f, 0x00, 0x01
	data1	0x11, 0x73, 0x1a, 0x00, 0x01, 0x11, 0x5f, 0x1a
	data1	0x00, 0x01, 0x11, 0x19, 0x00, 0x01, 0x18, 0x14
	data1	0x00, 0x05, 0x02
	data4.ua .abe$7.text
	data1	0x00, 0x01, 0x11, 0x0b, 0x00, 0x01, 0x11, 0x14
	data1	0x15, 0x00, 0x01, 0x11, 0x28, 0x00, 0x01, 0x11
	data1	0x14, 0x10, 0x00, 0x01, 0x11, 0x19, 0x00, 0x01
	data1	0x18, 0x14, 0x02, 0x04, 0x00, 0x01, 0x01

// ===
	.secalias .abe$12.note, ".note"
	.section .abe$12.note = "", "note"
	.align 4
	data1	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x5f
	data1	0x00, 0x00, 0x00, 0x01
	stringz	"HP"
	data1	0x00
	stringz	"A.06.25 [Nov 30 2009] [Build N/A *E001*]\necom options = -assembly on -ext on -lang c,c99 -ansi_for_scope on -inline_power 1 -link_type dynamic -fpeval float -fpevaldec _Decimal32 -tls_dyn on -target_os 11.31 --sys_include /usr/include -ucode hdriver=optlevel%1% -plusolistoption -Ol06all! -plusolistoption -Ol12direct! -plusolistoption -Ol13moderate! -plusooption -Oq01,al,ag,cn,sz,ic,vo,Mf,Po,es,rs,Rf,Pr,sp,in,cl,om,vc,pi,fa,pe,rr,pa,pv,nf,cp,lx,Pg,ug,lu,lb,uj,dn,sg,pt,kt,em,np,ar,rp,dl,fs,bp,wp,pc,mp,lr,cx,cr,pi,so,Rc,fa,ft,fe,ap,st,lc,Bl,sr,Qs,do,ib,pl,sd,ll,rl,dl,Lt,ol,fl,lm,ts,rd,Dp,If!\n1653977540"
	data1	0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00
	data1	0x24, 0x00, 0x00, 0x00, 0x04
	stringz	"HP"
	data1	0x00
	stringz	"t.c\n/root\nANSI C 32 bits\n1653977528"
           
  • LINUX
.file	"te.c"
	.pred.safe_across_calls p1-p5,p16-p63
	.section	.rodata,	"a",	"progbits"
	.align 8
.LC0:
	stringz	"!!!!!!!!!"
	.align 8
.LC1:
	stringz	"!!!!!!!!![%s]\n"
	.align 8
.LC2:
	stringz	"\273\341core\265\364\243\254\323\246\270\303\264\362\323\241\262\273\263\366\300\264"
	.section	.text,	"ax",	"progbits"
	.align 16
	.global BDPceshi#
	.proc BDPceshi#
BDPceshi:
	.prologue 14, 35
	.save ar.pfs, r36
	alloc r36 = ar.pfs, 3, 4, 3, 0
	.vframe r37
	mov r37 = r12
	adds r12 = -528, r12
	.save rp, r35
	mov r35 = b0
	mov r38 = r1
	.body
	;;
	mov r14 = r37
	;;
	st4 [r14] = r32
	adds r14 = 4, r37
	;;
	st4 [r14] = r33
	adds r14 = 8, r37
	;;
	st4 [r14] = r34
	adds r14 = -512, r37
	;;
	mov r39 = r14
	mov r40 = r0
	addl r41 = 512, r0
	br.call.sptk.many b0 = memset#
	mov r1 = r38
	;;
	addl r39 = @ltoffx(.LC0), r1
	;;
	ld8.mov r39 = [r39], .LC0
	br.call.sptk.many b0 = puts#
	mov r1 = r38
	adds r14 = 4, r37
	adds r15 = -512, r37
	;;
	mov r39 = r15
	ld4 r40 = [r14]
	br.call.sptk.many b0 = strcpy#
	mov r1 = r38
	;;
	addl r39 = @ltoffx(.LC1), r1
	;;
	ld8.mov r39 = [r39], .LC1
	adds r14 = -512, r37
	;;
	mov r40 = r14
	br.call.sptk.many b0 = printf#
	mov r1 = r38
	adds r14 = 4, r37
	adds r15 = -512, r37
	;;
	mov r39 = r15
	ld4 r40 = [r14]
	br.call.sptk.many b0 = strcpy#
	mov r1 = r38
	adds r14 = 4, r37
	;;
	ld4 r14 = [r14]
	;;
	addp4 r14 = 0,r14
	;;
	ld1 r14 = [r14]
	;;
	sxt1 r14 = r14
	;;
	cmp4.ne p6, p7 = 0, r14
	(p6) br.cond.dptk .L2
	addl r39 = @ltoffx(.LC2), r1
	;;
	ld8.mov r39 = [r39], .LC2
	br.call.sptk.many b0 = puts#
	mov r1 = r38
.L2:
	mov r14 = r0
	;;
	mov r8 = r14
	mov ar.pfs = r36
	mov b0 = r35
	.restore sp
	mov r12 = r37
	br.ret.sptk.many b0
	;;
	.endp BDPceshi#
	.section	.rodata,	"a",	"progbits"
	.align 8
.LC3:
	stringz	"\264\362\323\241\263\311\271\246"
	.section	.text,	"ax",	"progbits"
	.align 16
	.global main#
	.proc main#
main:
	.prologue 14, 32
	.save ar.pfs, r33
	alloc r33 = ar.pfs, 0, 4, 3, 0
	.vframe r34
	mov r34 = r12
	adds r12 = -272, r12
	.save rp, r32
	mov r32 = b0
	mov r35 = r1
	.body
	;;
	adds r14 = -240, r34
	adds r15 = -248, r34
	;;
	mov r36 = r14
	mov r37 = r0
	mov r38 = r15
	br.call.sptk.many b0 = BDPceshi#
	mov r1 = r35
	mov r14 = r8
	adds r15 = -256, r34
	;;
	st4 [r15] = r14
	adds r15 = -256, r34
	;;
	ld4 r14 = [r15]
	;;
	cmp4.eq p6, p7 = 0, r14
	(p6) br.cond.dptk .L9
	addl r36 = @ltoffx(.LC3), r1
	;;
	ld8.mov r36 = [r36], .LC3
	br.call.sptk.many b0 = puts#
	mov r1 = r35
.L9:
	;;
	mov ar.pfs = r33
	mov b0 = r32
	.restore sp
	mov r12 = r34
	br.ret.sptk.many b0
	;;
	.endp main#
	.ident	"GCC: (GNU) 4.2.3"
	.global strcpy#
	.type	strcpy#,@function
	.global puts#
	.type	puts#,@function
	.global printf#
	.type	printf#,@function
	.global memset#
	.type	memset#,@function
           

 經過上述一通折騰,是不是感覺有點頭暈眼花,血壓直接往上竄。加之 HPUX 下的彙編,網上資料風毛菱角,就在我快走進死胡同的時候,忽然靈光顯現,為何不用 GDB 跟蹤一下試試。

4. 柳岸花明(GDB 調試)

  • HPUNX
gdb t
HP gdb 6.3 for HP Itanium (32 or 64 bit) and target HP-UX 11iv2 and 11iv3.
Copyright 1986 - 2011 Free Software Foundation, Inc.
Hewlett-Packard Wildebeest 6.3 (based on GDB) is covered by the
GNU General Public License. Type "show copying" to see the conditions to
change it and/or distribute copies. Type "show warranty" for warranty/support.
..
(gdb) b main
Breakpoint 1 at 0x4000c00:1: file t.c, line 27 from /root/t.
(gdb) 
(gdb) run
Starting program: /root/t 
Breakpoint 1, main () at t.c:27
27              int iRe= BDPceshi(sDPACNO,NULL,&dFREAMT);
(gdb) s
BDPceshi (sDPACNO=0x200000007ffff090 "", sFRENUM=0x0, 
    dFREAMT=0x200000007ffff080) at t.c:7
7               memset(sDPACNO,0,sizeof(sDPACNO));
           

 當我看到

sFRENUM=0x0

時,我已恍然大悟,記得曾經在那本書上提到過此種情形,隻是時間久遠,當下已然記不起了,不過可以笃定的是,它就是問題的關鍵。抱着印證心中的想法,我又執行了下面的指令:

(gdb) x /x 0x0
0x0:    Error accessing memory address 0x0: Invalid argument.
(gdb) x /x 0x200000007ffff090
0x200000007ffff090:     0x00000180
(gdb) x /x 0x200000007ffff080
0x200000007ffff080:     0x00000000
(gdb) s
8               printf("!!!!!!!!!\n");
(gdb) s
!!!!!!!!!
11              sprintf(sDPACNO,"%s",sFRENUM);
(gdb) s
12              printf("!!!!!!!!![%s]\n",sDPACNO);
(gdb) s
!!!!!!!!![]
           

 通過分别通路 3 個參數資料存儲位址。再結合通路 0x0 位置系統給出的提示疑惑已解。下面又跟蹤了幾行,餘下部分省略。

 為了對比,也給出 LINUX 下跟蹤的結果,大家從中可以發現明顯的差別。

  • LINUX
gdb t
GNU gdb (GDB) EulerOS 9.2-1.ky10
Copyright (C) 2020 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-kylin-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from t...
(gdb) b main
Breakpoint 1 at 0x400701: file t.c, line 27.
(gdb) run
Starting program: /root/te/t 

Breakpoint 1, main () at t.c:27
27		int iRe= BDPceshi(sDPACNO,NULL,&dFREAMT);
(gdb) s
BDPceshi (sDPACNO=0x7fffffffe250 "", sFRENUM=0x0, dFREAMT=0x7fffffffe350) at t.c:7
7		memset(sDPACNO,0,sizeof(sDPACNO));
8		printf("!!!!!!!!!\n");
(gdb) s
!!!!!!!!!
11		sprintf(sDPACNO,"%s",sFRENUM);
(gdb) s

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e990d0 in ?? () from /usr/lib64/libc.so.6
           

 程式執行到這裡收到了一個 Segmentation fault(段錯誤)系統信号并中止了程式的運作。

5. 通路 NULL 指針錯誤背後的原理

 标準定義了 NULL 指針,它作為一個特殊指針變量,表示不指向任何東西。要使一個指針變量為 NULL,你可以給它賦一個 0 值。為了測試一個指針變量是否為 NULL,你可以将它與 0 值進行比較。之是以選擇零這個值是因為一種源代碼約定。就機器内部而言,NULL 指針的實際值可能與此不同。在這種情況下,編譯器将負責零值和内部值之間的翻譯轉換。

 如果對一個 NULL 指針進行間接通路會發生什麼情況呢?它的結果因編譯器而異,在有些機器上,它會通路記憶體位置 0,編譯器能夠確定記憶體位置 0 沒有存儲任何變量,但機器并未妨礙你通路或修改這個位置(HPUNIX 下的 cc 采用的政策)。這種行為是非常不幸的,因為程式包含了一個錯誤,但機器卻隐匿了它的症狀,這樣就使這個錯誤更加難以尋找(“起碼看起來像”沒問題的由來)。

 在其他機器上(LINUX 下的 gcc 采用的政策),對 NULL 指針進行間接通路将引發一個錯誤(核心發送 SIGSEGV 信号),并終止程式。宣布這個錯誤比隐藏這個錯誤要好得多,因為程式員能夠更容易修正它。

 為了進一步驗證,我還在 HPUNIX 下安裝了 gcc 運作結果和在 LINUX 下如出一轍。

總結

 至此,因程式員在使用 C 語言時,未能遵守相關規定,寫下了本可避免潛在的問題,導緻此次程式移植難其背後根源終于找到了。但我一點也高興不起來,因為要排除散落在程式各處這類問題,将是接下來的挑戰。為了研究這個問題,我查了很多資料,最大的感悟是風格良好的程式會在指針解引用之前對它進行檢查,這種看似麻煩的政策可以節省大量的調試時間,同樣不能過度依賴某些編譯器的特性(此次 HPUNIX 下的 cc),這會為以後程式的運作、移植埋下隐患。還是那句話:相信我,你節省的那點時間,必将成倍反噬于你。