32位应用程序转64位笔记

更新记录

正文

最近做一些32位应用程序编译到64位应用的工作,环境是centos 7.9,程序都是用c语言编写,使用gcc编译。

查看相关信息

  • 查看gcc版本
1
2
3
4
5
6
7
8
# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --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++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
  • 查看系统位数
1
2
3
4
5
6
# uname -a
Linux d5c159febf78 5.4.0-109-generic #123-Ubuntu SMP Fri Apr 8 09:10:54 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
# getconf WORD_BIT
32
# getconf LONG_BIT
64

x86_64代表系统是64位的;通过查看WORD_BIT和LONG_BIT也可以判断,32位系统两者均为32,64位系统其值分别为32和64。

  • 查看文件的位数

    • 方法1:file filename

    • 方法2:readelf -h filename

编译

64位的gcc,默认编译64位程序,如果需要编译32位,那么添加-m32即可

修改原则

修改参考以下内容,后续从中选择补充到本文

转换应用程序以适用于 64 位环境

实际修订的内容总结

本节内容根据实际遇到的问题编写

va_list

定义不同

va_list在c里面用于可变参数,在32位应用场景下编译是一个指针,而在64位应用中的实现则是一个结构体。

因此,在32位程序中可以直接赋值,也可以重复使用va_list变量;但是如果进行64位编译,那么连编译都无法通过。

va_list位于stdarg.h

查看stdarg.h文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*===---- stdarg.h - Variable argument handling ----------------------------===
*
* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
* See https://llvm.org/LICENSE.txt for license information.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*
*===-----------------------------------------------------------------------===
*/

#ifndef __STDARG_H
#define __STDARG_H

#ifndef _VA_LIST
typedef __builtin_va_list va_list;
#define _VA_LIST
#endif
#define va_start(ap, param) __builtin_va_start(ap, param)
#define va_end(ap) __builtin_va_end(ap)
#define va_arg(ap, type) __builtin_va_arg(ap, type)

/* GCC always defines __va_copy, but does not define va_copy unless in c99 mode
* or -ansi is not specified, since it was not part of C90.
*/
#define __va_copy(d,s) __builtin_va_copy(d,s)

#if __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L || !defined(__STRICT_ANSI__)
#define va_copy(dest, src) __builtin_va_copy(dest, src)
#endif

#ifndef __GNUC_VA_LIST
#define __GNUC_VA_LIST 1
typedef __builtin_va_list __gnuc_va_list;
#endif

#endif /* __STDARG_H */

发现定义还是比较简单,__builtin_va_list看起来和平台有关,继续找实现没有找到很具体的。参考别人的博客linux c va_list 32位和64位的差异和自己用gdb看到的数据结构

1
2
p *stAp
$2 = {gp_offset = 48, fp_offset = 48, overflow_arg_area = 0x7fffc73ea210, reg_save_area = 0x7fffc73ea140}

基本可以确定在32位系统下,va_list的定义如下:

1
2
//此处只是示意,并非实际定义
typedef va_list char**;

在64位系统中,va_list是一个结构体数组,通过数组记录偏移量

1
2
3
4
5
6
7
8
// 此处只是示意,并非实际定义
typedef struct
{
unsigned int gp_offset;
unsigned int fp_offset;
void * overflow_arg_area;
void * reg_save_area;
} va_list[1];

用法修改

  • 直接赋值

    • stdarg.h提供了va_copy()接口用于复制va_list

    • 错误用法(指32位允许,64位不允许)

      1
      va_list bak = p_list;
    • 正确的用法

      1
      2
      3
      4
      5
      va_list bak;

      va_copy(bak, p_list);
      //use bak do somethine
      va_end(bak);
  • 重复使用

    • 类似vsnprintf()一类的接口会修改va_list的偏移量。由于在32位系统下va_list是一个指针,因此传入接口的值传递不影响调用方重复使用;但是在64位应用程序当中,传入va_list结构体会使得内部偏移量变化,在重复使用时就会指到错误的内存地址

    • 错误用法

      1
      2
      3
      4
      5
      6
      7
      va_list p_list;

      va_start(p_list, fmt);
      //重复调用
      vsnprintf(..., p_list);
      vsnprintf(..., p_list);
      va_end(p_list);
    • 正确用法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      va_list p_list;
      va_list bak;

      va_start(p_list, fmt);
      //重复调用
      va_copy(bak, p_list);
      vsnprintf(..., p_list);
      va_end(p_list);
      vsnprintf(..., bak);
      va_end(bak);

32位类型和64位类型的转换

在32位应用程序当中,int,long,指针均为32位;但是在64位应用程序中,int为32位,long和指针变成了64位。由于位数的变化,部分变量间的转换会出现问题。下面举例一部分不好的地方:

问题一:用void *指针存储数值

void *指针不指定类型,在32位应用中有不少图方便直接用其存储数值的使用,在编译成64位时会引发gcc警告。

解决方案一:数值存储前先转换成intptr_t类型

解决方案二:非临时变量传给void *其地址

问题二:用int型直接存储了指针

由于32位应用int和指针长度一样,可以指针存储指针,有时也做指针用int存储的操作。编译成64位应用时,指针就会被截断导致无效。如果一定要存储用intptr_t存储

问题三:只是需要布尔值

代码片段

1
__RegResponse(arg1, arg2, (int) p, p);

此处第3个参数在函数的实现当中只是做一个布尔值的逻辑判断,此处强转为整形在64位下会引发gcc警告。可以直接使用!!对p指针做两次逻辑运算,自然获得了合法的布尔值,如下:

1
__RegResponse(arg1, arg2, !!p, p);

问题四:位数变化导致隐式强转结果错误

代码片段:

1
2
3
4
5
6
7
8
intptr_t *dst = data + HEAD_ALL_LEN;

dst[0] = ntohl(dst[0]);
dst[1] = ntohl(dst[1]);

if (dst[0] == -1) {
...
}

此处intptr_t在32位应用中为有符号32位整数,当编译成64位时,变成了64位有符号整数。而ntohl的返回值是无符号32位整数。

dst[0]中存储的值为网络字节序的 -1, 转换成主机字节序的u32数据时,其值为UINT32_MAX。这个值在强制类型转换为有符号32位整数时,自动就编码成了-1,结果正确;但是在64位应用中,低32位完全可以容纳UINT32_MAX,导致其值不为 -1,使得程序逻辑出现问题。

问题五:%ld%lld

使用printf指定格式的时候,64位变量在32位应用要用%lld,但是在64位应用只需要%ld即可。为了解决这种不一致问题,可以使用%"PRId32%"PRId64代替。


32位应用程序转64位笔记
http://nyamori.icu/2022/05/27/32位应用程序转64位笔记/
作者
Nyamori
发布于
2022年5月27日
许可协议