VOOZH about

URL: https://qiita.com/tobira-code/items/a03f39a02678d80bbd26

⇱ Byte swapping(エンディアン変換)を理解する #Linux - Qiita


👁 Image
47

Go to list of users who liked

46

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

More than 5 years have passed since last update.

@tobira-code

Byte swapping(エンディアン変換)を理解する

47
Last updated at Posted at 2017-02-12

はじめに

Byte Swapping(エンディアン変換)について説明します。

エンディアンとは何か、
どのようなときにByte swappingが必要になるのかを説明します。

エンディアンとは

メモリアドレスはbyte単位で割り振られます。

例えば
Addr 0x100000を指定すると
そのアドレスにある1byteが特定できます。
Addr 0x100001を指定すると
そのアドレスにある1byteが特定できます。

一方で
2byte以上のデータ型はbyte境界を跨ってデータを配置
する必要があります。

例えば
2byteの符号なし整数(値は0x1234とします)をメモリに配置することを考えます。
Addr 0x100000 = 0x12, 0x100001 = 0x34 とする流儀と、 .. ビッグ
Addr 0x100000 = 0x34, 0x100001 = 0x12 とする流儀   .. リトル
の2つの方法が考えられます。
それぞれ、ビッグエンディアン、リトルエンディアンといいます。

表現を変えると
データの先頭byteを小さいアドレスに置く方式⇒ビッグエンディアン
データの先頭byteを大きいアドレスに置く方式⇒リトルエンディアン
とも言えます。

どちらかの流儀に統一されればよいのですが、
悲しいことにどちらの流儀も世の中に普及しています。

Intel CPUアーキテクチャはリトルエンディアン
TCP / IPプロトコルはビッグエンディアン
となっています。

ビッグエンディアンのデータをバイナリ形式でみると、
左側に先頭バイトが表示されるため可読性がよいです。
例えば、2byteの符号なし整数0x1234は
ビッグエンディアンでは0x1234は0x12 0x34と表示されますが、
リトルエンディアンでは0x1234は0x34 0x12と表示されます。
ビッグエンディアンがしばしば使われるのはこのような理由からかもしれません。

2byte以上のデータを扱うときには、
何らかの方法でどちらの流儀かの情報を取得する
もしくは、
暗黙の仮定を置く
ことになります。

なお、エンディアンと同じ意味で、
endianness / byte orderという表現も使われます。

Byte swappingとは

エンディアンを相互に変換すること、つまり、
ビッグエンディアン⇒リトルエンディアン
または
リトルエンディアン⇒ビッグエンディアン
のことをByte swappingといいます。エンディアン変換とも言います。
Byte順番を並び変えるためswappingと表現されます。

2byte / 4byteの例を次に示します。
👁 image

Byte swappingが必要になるケース

Byte swappingはどのようなケースで必要となるのでしょうか。

外部のデータを扱わないプログラムは
Byte swappingの必要はありません。

ネットワークやファイルなど、外部のデータを扱うケース
かつ、
外部のデータとCPUアーキテクチャのエンディアンが違う
ときにByte swappingが必要となります。

実装

Byte swappingの実装例を示します。
2byte / 4byteの符号なし整数をbyte swappingします。
使用したプロセッサ / OS / gccは以下です。

Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz 64bit
Ubuntu14.04 64bit
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

(*)4つのspaceをtabに置き換えてください。

makefile
CFLAGS=-g -O0 -I. -Wextra -Wno-unused-parameter -Wall -Werror
CC=gcc
INCS=test.h
OBJS=test.o
LIBS=#-lpthread -lm
TARGET=test

%.o: %.c $(INCS)
>---$(CC) -c -o $@ $<

$(TARGET): $(OBJS)
>---$(CC) -o $@ $^ $(LIBS)

clean:
>---rm -rf $(TARGET) *.o
test.c
# include <stdio.h>
# include <stdint.h>

inline uint16_t swap16(uint16_t value)
{
 uint16_t ret;
 ret = value << 8;
 ret |= value >> 8;
 return ret;
}

inline uint32_t swap32(uint32_t value)
{
 uint32_t ret;
 ret = value << 24;
 ret |= (value&0x0000FF00) << 8;
 ret |= (value&0x00FF0000) >> 8;
 ret |= value >> 24;
 return ret;
}

int main(int argc, char* argv[])
{
 uint16_t val16;
 uint32_t val32;
 if (argc != 2) {
 return -1;
 }
 if (sscanf(argv[1], "%x", &val32) == -1) {
 return -1;
 }
 val16 = (uint16_t)(val32>>16);
 printf("0x%04x : 0x%04x\n", val16, swap16(val16));
 printf("0x%08x : 0x%08x\n", val32, swap32(val32));
 return 0;
}

実行

./test
$ make clean
$ make
$ ./test 12345678
0x1234 : 0x3412
0x12345678 : 0x78563412

アセンブラ(-O0;最適化なし)

objdumpでアセンブラを出力してみます。

$ objdump -d test.o
assembler
0000000000000000 <swap16>:
 0:	55 	push %rbp
 1:	48 89 e5 	mov %rsp,%rbp
 4:	89 f8 	mov %edi,%eax
 6:	66 89 45 ec 	mov %ax,-0x14(%rbp)
 a:	0f b7 45 ec 	movzwl -0x14(%rbp),%eax
 e:	c1 e0 08 	shl $0x8,%eax
 11:	66 89 45 fe 	mov %ax,-0x2(%rbp)
 15:	0f b7 45 ec 	movzwl -0x14(%rbp),%eax
 19:	66 c1 e8 08 	shr $0x8,%ax
 1d:	66 09 45 fe 	or %ax,-0x2(%rbp)
 21:	0f b7 45 fe 	movzwl -0x2(%rbp),%eax
 25:	5d 	pop %rbp
 26:	c3 	retq 

0000000000000027 <swap32>:
 27:	55 	push %rbp
 28:	48 89 e5 	mov %rsp,%rbp
 2b:	89 7d ec 	mov %edi,-0x14(%rbp)
 2e:	8b 45 ec 	mov -0x14(%rbp),%eax
 31:	c1 e0 18 	shl $0x18,%eax
 34:	89 45 fc 	mov %eax,-0x4(%rbp)
 37:	8b 45 ec 	mov -0x14(%rbp),%eax
 3a:	25 00 ff 00 00 	and $0xff00,%eax
 3f:	c1 e0 08 	shl $0x8,%eax
 42:	09 45 fc 	or %eax,-0x4(%rbp)
 45:	8b 45 ec 	mov -0x14(%rbp),%eax
 48:	25 00 00 ff 00 	and $0xff0000,%eax
 4d:	c1 e8 08 	shr $0x8,%eax
 50:	09 45 fc 	or %eax,-0x4(%rbp)
 53:	8b 45 ec 	mov -0x14(%rbp),%eax
 56:	c1 e8 18 	shr $0x18,%eax
 59:	09 45 fc 	or %eax,-0x4(%rbp)
 5c:	8b 45 fc 	mov -0x4(%rbp),%eax
 5f:	5d 	pop %rbp
 60:	c3 	retq 

0000000000000061 <main>:
 61:	55 	push %rbp
 62:	48 89 e5 	mov %rsp,%rbp
 65:	48 83 ec 20 	sub $0x20,%rsp
 69:	89 7d ec 	mov %edi,-0x14(%rbp)
 6c:	48 89 75 e0 	mov %rsi,-0x20(%rbp)
 70:	83 7d ec 02 	cmpl $0x2,-0x14(%rbp)
 74:	74 07 	je 7d <main+0x1c>
 76:	b8 ff ff ff ff 	mov $0xffffffff,%eax
 7b:	eb 7f 	jmp fc <main+0x9b>
 7d:	48 8b 45 e0 	mov -0x20(%rbp),%rax
 81:	48 83 c0 08 	add $0x8,%rax
 85:	48 8b 00 	mov (%rax),%rax
 88:	48 8d 55 fc 	lea -0x4(%rbp),%rdx
 8c:	be 00 00 00 00 	mov $0x0,%esi
 91:	48 89 c7 	mov %rax,%rdi
 94:	b8 00 00 00 00 	mov $0x0,%eax
 99:	e8 00 00 00 00 	callq 9e <main+0x3d>
 9e:	83 f8 ff 	cmp $0xffffffff,%eax
 a1:	75 07 	jne aa <main+0x49>
 a3:	b8 ff ff ff ff 	mov $0xffffffff,%eax
 a8:	eb 52 	jmp fc <main+0x9b>
 aa:	8b 45 fc 	mov -0x4(%rbp),%eax
 ad:	c1 e8 10 	shr $0x10,%eax
 b0:	66 89 45 fa 	mov %ax,-0x6(%rbp)
 b4:	0f b7 45 fa 	movzwl -0x6(%rbp),%eax
 b8:	89 c7 	mov %eax,%edi
 ba:	e8 00 00 00 00 	callq bf <main+0x5e>
 bf:	0f b7 d0 	movzwl %ax,%edx
 c2:	0f b7 45 fa 	movzwl -0x6(%rbp),%eax
 c6:	89 c6 	mov %eax,%esi
 c8:	bf 00 00 00 00 	mov $0x0,%edi
 cd:	b8 00 00 00 00 	mov $0x0,%eax
 d2:	e8 00 00 00 00 	callq d7 <main+0x76>
 d7:	8b 45 fc 	mov -0x4(%rbp),%eax
 da:	89 c7 	mov %eax,%edi
 dc:	e8 00 00 00 00 	callq e1 <main+0x80>
 e1:	89 c2 	mov %eax,%edx
 e3:	8b 45 fc 	mov -0x4(%rbp),%eax
 e6:	89 c6 	mov %eax,%esi
 e8:	bf 00 00 00 00 	mov $0x0,%edi
 ed:	b8 00 00 00 00 	mov $0x0,%eax
 f2:	e8 00 00 00 00 	callq f7 <main+0x96>
 f7:	b8 00 00 00 00 	mov $0x0,%eax
 fc:	c9 	leaveq 
 fd:	c3 	retq 

アセンブラ(-O2;最適化あり)

最適化オプションを-O2にして再度コンパイルしてアセンブラを出力します。
swap16 / swap32がコンパクトになりました。

assembler
0000000000000000 <swap16>:
 0:	89 f8 	mov %edi,%eax
 2:	66 c1 c0 08 	rol $0x8,%ax
 6:	c3 	retq 
 7:	66 0f 1f 84 00 00 00 	nopw 0x0(%rax,%rax,1)
 e:	00 00 

0000000000000010 <swap32>:
 10:	89 f8 	mov %edi,%eax
 12:	0f c8 	bswap %eax
 14:	c3 	retq 

0000000000000000 <main>:
 0:	83 ff 02 	cmp $0x2,%edi
 3:	75 64 	jne 69 <main+0x69>
 5:	48 83 ec 18 	sub $0x18,%rsp
 9:	48 8b 7e 08 	mov 0x8(%rsi),%rdi
 d:	31 c0 	xor %eax,%eax
 f:	48 8d 54 24 0c 	lea 0xc(%rsp),%rdx
 14:	be 00 00 00 00 	mov $0x0,%esi
 19:	e8 00 00 00 00 	callq 1e <main+0x1e>
 1e:	83 f8 ff 	cmp $0xffffffff,%eax
 21:	74 4a 	je 6d <main+0x6d>
 23:	0f b7 54 24 0e 	movzwl 0xe(%rsp),%edx
 28:	be 00 00 00 00 	mov $0x0,%esi
 2d:	bf 01 00 00 00 	mov $0x1,%edi
 32:	89 d0 	mov %edx,%eax
 34:	89 d1 	mov %edx,%ecx
 36:	c1 e0 08 	shl $0x8,%eax
 39:	66 c1 e9 08 	shr $0x8,%cx
 3d:	09 c1 	or %eax,%ecx
 3f:	31 c0 	xor %eax,%eax
 41:	0f b7 c9 	movzwl %cx,%ecx
 44:	e8 00 00 00 00 	callq 49 <main+0x49>
 49:	8b 54 24 0c 	mov 0xc(%rsp),%edx
 4d:	be 00 00 00 00 	mov $0x0,%esi
 52:	bf 01 00 00 00 	mov $0x1,%edi
 57:	31 c0 	xor %eax,%eax
 59:	89 d1 	mov %edx,%ecx
 5b:	0f c9 	bswap %ecx
 5d:	e8 00 00 00 00 	callq 62 <main+0x62>
 62:	31 c0 	xor %eax,%eax
 64:	48 83 c4 18 	add $0x18,%rsp
 68:	c3 	retq 
 69:	83 c8 ff 	or $0xffffffff,%eax
 6c:	c3 	retq 
 6d:	83 c8 ff 	or $0xffffffff,%eax
 70:	eb f2 	jmp 64 <main+0x64>

rol

swap16ではrol命令が使われています。

0000000000000000 <swap16>:
 0:	89 f8 	mov %edi,%eax
 2:	66 c1 c0 08 	rol $0x8,%ax
 6:	c3 	retq 
 7:	66 0f 1f 84 00 00 00 	nopw 0x0(%rax,%rax,1)
 e:	00 00 

rolは左方向にビットローテションする命令です。
8ビットローテションすることで2 Byte Swappingを実現しています。

図で示します。
rol 1すると全体に1ビット左にずれて、左端のb15が右端に移動します。
rol 8すると全体が8ビット左にずれて、左側の8ビットが右側にきます。
2 byte swappingできます。
👁 image

bswap

swap32ではbswap命令が使われています。

0000000000000010 <swap32>:
 10:	89 f8 	mov %edi,%eax
 12:	0f c8 	bswap %eax
 14:	c3 	retq 

bswapはレジスタの値をByte Swappingする命令です。

なお、movbeという命令もあり、こちらは
メモリからレジスタにデータを読み込んだり、
レジスタからメモリにデータを書き出したり

するときにByte swappingできます。

47

Go to list of users who liked

46
2

Go to list of comments

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47

Go to list of users who liked

46