NULL 指针在不同平台下的表现引发程序报错(C 语言)

 作者:高玉涵
 时间: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),这会为以后程序的运行、移植埋下隐患。还是那句话:相信我,你节省的那点时间,必将成倍反噬于你。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值