アセンブラでエンディアン変換(その2)

前回は、アセンブラで試行錯誤してエンディアン変換してみました。
先人たちは、どのようにエンディアン変換をプログラミングしているのか気になります。
標準ライブラリのhtonlのソースを読んで、確認したいと思います。

htonlとは、host to networkのバイトオーダー long(32bit)版ですね。

takk@deb9:~$ man htonl
BYTEORDER(3)               Linux Programmer's Manual              BYTEORDER(3)

名前
       htonl,  htons,  ntohl, ntohs - ホストバイトオーダーとネットワークバイト
       オーダーの間で値を変換する

書式
       #include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);

説明
       htonl()  関数は unsigned integer hostlong  を  ホストバイトオーダーから
       ネットワークバイトオーダーに変換する。

~省略~

ソースを取得。当然glibcですね。

takk@deb9:~/src$ apt-get source glibc

~省略~

takk@deb9:~/src$ cd source glibc-2.24
takk@deb9:~/src/glibc-2.24$

htonlの関数を探します。

takk@deb9:~/src/glibc-2.24$ find -name hton*
./sysdeps/ia64/htonl.S
./sysdeps/ia64/htons.S
./sysdeps/alpha/htonl.S
./sysdeps/alpha/htons.S
./sysdeps/i386/htonl.S
./sysdeps/i386/htons.S
./sysdeps/x86_64/htonl.S
./inet/htons.c
./inet/htontest.c
./inet/htonl.c
takk@deb9:~/src/glibc-2.24$

ソース全文です。

takk@deb9:~/src/glibc-2.24$ cat -n inet/htonl.c
     1  /* Copyright (C) 1993-2016 Free Software Foundation, Inc.
     2     This file is part of the GNU C Library.
     3
     4     The GNU C Library is free software; you can redistribute it and/or
     5     modify it under the terms of the GNU Lesser General Public
     6     License as published by the Free Software Foundation; either
     7     version 2.1 of the License, or (at your option) any later version.
     8
     9     The GNU C Library is distributed in the hope that it will be useful,
    10     but WITHOUT ANY WARRANTY; without even the implied warranty of
    11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    12     Lesser General Public License for more details.
    13
    14     You should have received a copy of the GNU Lesser General Public
    15     License along with the GNU C Library; if not, see
    16     <http://www.gnu.org/licenses/>.  */
    17
    18  #include <stdint.h>
    19  #include <netinet/in.h>
    20
    21  #undef  htonl
    22  #undef  ntohl
    23
    24  uint32_t
    25  htonl (uint32_t x)
    26  {
    27  #if BYTE_ORDER == BIG_ENDIAN
    28    return x;
    29  #elif BYTE_ORDER == LITTLE_ENDIAN
    30    return __bswap_32 (x);
    31  #else
    32  # error "What kind of system is this?"
    33  #endif
    34  }
    35  weak_alias (htonl, ntohl)
takk@deb9:~/src/glibc-2.24$

内部で、__bswap_32関数を使ってます。(もっと掘り下げると、__builtin_bwap32ですが)
では、この__bswap_32を使って簡単なエンディアン変換プログラムを作ってみます。

takk@deb9:~/tmp$ cat -n t.c
     1  #include <stdio.h>
     2  #include <stdint.h>
     3  #include <netinet/in.h>
     4
     5  int main()
     6  {
     7          unsigned long a;
     8          unsigned char *p;
     9          unsigned char tmp[4];
    10          a = 0x12345678;
    11          a = __bswap_32(a);
    12          printf("%08x\n",a);
    13  }
takk@deb9:~/tmp$ ./a.out
78563412
takk@deb9:~/tmp$

エンディアン変換はうまくいきました。
アセンブラを見てみましょう。まずは、main関数。

00000000000006be <main>:
 6be:   55                      push   %rbp
 6bf:   48 89 e5                mov    %rsp,%rbp
 6c2:   48 83 ec 10             sub    $0x10,%rsp
 6c6:   48 c7 45 f8 78 56 34    movq   $0x12345678,-0x8(%rbp)
 6cd:   12
 6ce:   48 8b 45 f8             mov    -0x8(%rbp),%rax
 6d2:   89 c7                   mov    %eax,%edi
 6d4:   e8 d7 ff ff ff          callq  6b0 <__bswap_32>
 6d9:   89 c0                   mov    %eax,%eax
 6db:   48 89 45 f8             mov    %rax,-0x8(%rbp)
 6df:   48 8b 45 f8             mov    -0x8(%rbp),%rax
 6e3:   48 89 c6                mov    %rax,%rsi
 6e6:   48 8d 3d 97 00 00 00    lea    0x97(%rip),%rdi        # 784 <_IO_stdin_used+0x4>
 6ed:   b8 00 00 00 00          mov    $0x0,%eax
 6f2:   e8 69 fe ff ff          callq  560 <printf@plt>
 6f7:   b8 00 00 00 00          mov    $0x0,%eax
 6fc:   c9                      leaveq
 6fd:   c3                      retq
 6fe:   66 90                   xchg   %ax,%ax

6d4行で__bswap_32がコールされています。__bswap_32のアセンブラについても見てみます。

00000000000006b0 <__bswap_32>:
 6b0:   55                      push   %rbp
 6b1:   48 89 e5                mov    %rsp,%rbp
 6b4:   89 7d fc                mov    %edi,-0x4(%rbp)
 6b7:   8b 45 fc                mov    -0x4(%rbp),%eax
 6ba:   0f c8                   bswap  %eax
 6bc:   5d                      pop    %rbp
 6bd:   c3                      retq

とても少ないコードです。よく見ると、bswapという命令を使ってますね。 これを使えば、簡単にエンディアン変換してくれそうです。
では、bswap命令を、Cプログラムに直接埋め込んで使ってみましょう。

takk@deb9:~/tmp$ cat -n t.c
     1  #include <stdio.h>
     2  int main()
     3  {
     4          unsigned long a;
     5          asm("movq $0x12345678,-0x8(%rbp)");
     6          asm("mov -0x8(%rbp), %eax");
     7          asm("bswap %eax");
     8          asm("mov %eax,-0x8(%rbp)");
     9          printf("%08x\n",a);
    10  }
takk@deb9:~/tmp$

実行してみます。

takk@deb9:~/tmp$ gcc t.c
takk@deb9:~/tmp$ ./a.out
78563412
takk@deb9:~/tmp$

エンディアン変換って、CPUに命令が存在すれば、ものすごく簡単にできてしまうものなんですね。

Leave a Reply

Your email address will not be published. Required fields are marked *

CAPTCHA