在Linux用户空间支持字节序

4
我正在Linux上用C语言编写一个程序,以分析从嵌入式系统生成的核心文件。这些核心文件可能是小端(ARM)或大端(MIPS),而分析它们的程序可能在小端主机(x86)或大端(PowerPC)上运行。
通过查看头文件,我知道核心是LE还是BE。我希望我的程序不需要知道它运行的主机是小端还是大端,我想使用API来处理它。如果没有更好的选择,我猜我将开始依赖于#ifdef __BIG_ENDIAN__。
在Linux内核中,我们有cpu_to_le32等函数将本地字节顺序转换为小端等等。在用户空间中,有htonl等函数将本地字节顺序转换为大端,但我找不到本地字节顺序转换为小端的等效函数。
有人能建议一个适合用户空间的API吗?
编辑:为了明确起见,我正在寻找一个API,它已经知道我的CPU是大端还是小端,并相应地交换字节。我不想在代码中使用#ifdefs。我不仅仅是在寻找交换字节的代码片段;谢谢你提供这些,但那不是重点。

但是,使用这些代码片段,你可以轻松地编写一个宏来实现此功能,从而创建自己的“API”,不是吗? - orip
这是什么类型的嵌入式系统?你不能简单地使用一些交叉GDB来查看核心文件吗? - sigjuice
我可以编写自己的API吗?当然可以。我只是想避免使用#ifdef __BIG_ENDIAN__,如果有更干净、更易于维护的方法通过一个已经知道我的CPU是大端还是小端的现有API来完成它。 - DGentry
关于嵌入式系统的类型:它是一个完全正常和无聊的PowerPC。我已经为它编译了交叉gdb。gdb在许多方面都很棒,但并非万能。例如,在搜索特定模式的所有内存时,它表现得极其糟糕;你需要编写一个gdb宏来完成搜索中等大小的核心文件,这需要漫长的时间才能完成。 - DGentry
这里解释了一种避免使用 #ifdef BIG_ENDIAN 的方法:https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html - Sandburg
9个回答

19

#include <arpa/inet.h> 是很好的且可移植的,但仅保证对于{ntoh,hton}{s,l}的转换。如果需要在64位值上进行转换,或在大端字节序上进行翻转(其中ntohhton不起作用),这就不够了。

在Linux(glibc)上,#include <endian.h>提供了适合当前机器的定义。

htobe16  be16toh    htole16  le16toh
htobe32  be32toh    htole32  le32toh
htobe64  be64toh    htole64  le64toh

在 *BSD 上,#include <sys/endian.h> 提供了相同的宏定义。


5

如果您可以访问Neon协处理器,而且内存是连续的(例如视频帧),那么可以使用q寄存器(128字节)对帧执行swap16操作。当然,您需要注意对齐问题。

void swap16(void *__restrict src16)
{
    const void *start = src16;
    const void *end = src16 + FRAME_SIZE;
    asm volatile (
        "1: pld     [%0, #0x100]\n"
        "vld2.8         {q0,q1}, [%0]\n"
        "vmov           q2,q0\n"
        "vst2.8         {q1,q2}, [%0]!\n"
        "cmp            %1,%0\n"
        "bne            1b\n"
        : /* empty output operands */
        : "r" (start), "r" (end)
        : "cc", "memory"
        );
}

3

根据您要做的实际工作(读取ELF核心转储文件而无需担心大小端问题),我认为使用libelf是一个不错的选择。该库可在此处下载:这里,并提供了一份详细的教程:这里

该库能够透明地处理大小端的ELF文件,并且尽管其源码来自FreeBSD,它在Linux下也可以正常运行(您只需要进行通常的"./configure"和"make"过程即可构建它)。为了体验一下,我还尝试了“读取程序头表”的示例(稍作修改以使其编译通过),对于x86核心文件和MIPS大端核心文件,它似乎都可以“轻松应对”。


我一直在使用libbfd打开核心并定位每个部分,但对于内容,它只是提供了一个无符号字符的大数组(留给我处理其中任何内容的字节序问题)。我将尝试改用libelf,也许它在这方面更有帮助。 - DGentry
elf32_xlatetom()和elf32_xlatetof()函数可能对此有用,API的文档可以在http://docs.sun.com/app/docs/doc/816-5172/6mbb7btp9?a=view中找到。 - Lance Richardson

3
如果您将文件视为字节数组,则可以控制选择字节的顺序,并且您CPU的字节序实际上并不重要。在处理对齐问题方面,这也非常有用。您的核心转储可能包含未对齐的引用。我知道这很不可能,但这也可能是由于损坏造成的。将文件视为字节数组可以避免此类问题。我在实施jhead时使用了这种方法。

2
您可以使用apa/inet.h中的标准网络字节序转换函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); // Host to network
uint16_t htons(uint16_t hostshort); // Host to network
uint32_t ntohl(uint32_t netlong); // Network to host
uint16_t ntohs(uint16_t netshort); // Network to host

网络字节顺序是大端序。因此,这些函数的含义是:

hton*: Host endian to big endian
ntoh*: Big endian to host endian

希望这可以帮到你。

1
为什么需要API来完成这个任务?只需编写自己的函数调用htonl()(或任何产生BE的函数),然后简单地反转字节即可。听起来并不难。
类似这样:
union {
    struct {
        unsigned char c0;
        unsigned char c1;
        unsigned char c2;
        unsigned char c3;
    } ch;
    uint32_t ui;
} u;
unsigned char t;

u.ui = htonl (hostlong);
t = u.ch.c0; u.ch.c0 = u.ch.c3 ; u.ch.c3 = t;
t = u.ch.c1; u.ch.c1 = u.ch.c2 ; u.ch.c2 = t;

1

你可以自己编写代码(这些是基于苹果的例程):

static inline uint16_t Swap16(uint16_t x)
{
    return ( (x << 8) | (x >> 8) );
}

static inline uint32_t Swap32(uint32_t x)
{
    return ( (((x ^ (x >> 16 | (x << 16))) & 0xff00ffff) >> 8) ^ (x >> 8 | data << 24) );
}

然后您可以定义条件宏:

#ifdef __BIG_ENDIAN__
# define htols(x) Swap16(x)
# define htoll(x) Swap32(x)
#else
# define htols(x) (x)
# define htoll(x) (x)
#endif

如果你对Intel汇编代码感到满意,你甚至可以这样做:
// Swap16 is unchanged

static inline uint32_t Swap32(uint32_t x)
{
    __asm__ ("bswap %0" : "+r" (x));
    return ( x );
}
#ifdef __i386__
static inline uint64_t Swap64(uint64_t x)
{
    __asm__ ("bswap  %%eax\n\t"
             "bswap  %%edx\n\t"
             "xchgl  %%eax, %%edx"
             : "+A" (x));
    return ( x );
}
#elif defined(__x86_64__)
static inline uint64_t Swap64( uint64_t x )
{
    __asm__ ("bswap  %0" : "+r" (x));
    return ( x );
}
#endif

1

请查看 /usr/include/linux/byteorder/ 中提供的内核头文件,例如 __cpu_to_be32() 和 __be32_to_cpu()

同时,请查看 /usr/include/linux/types.h 文件,您可以在其中定义显式的大/小端整数类型,这将有助于检测编译时的任何不匹配。


2
不错的发现。然而,从快速查看来看,似乎你仍然需要类似于“#ifdef __BIG_ENDIAN__”这样的条件语句,以有条件地包含linux/byteorder/little_endian.h或linux/byteorder/big_endian.h。虽然不是很大的问题,但还是需要注意一下... - Lance Richardson
没错,但更好的方法是不使用 #ifdef BIG_ENDIAN 来编写代码,请参考 https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html。 - Sandburg

1

鉴于转换字节序很容易, 我总是使用类似于此的自定义代码,严格遵守在代码中使用哪种表示法的规则,并在端点(输入和输出)处理字节序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接