Rubyで国際化ドメインを変換

日本語.jpみたいなUラベルを含むドメイン名をpunycodeでA-Labelのドメイン名に変換する方法。

simpleidnというGemを使う。

インストール

$ gem install simpleidn

コード例

require 'simpleidn'

SimpleIDN.to_ascii("日本語.jp")           #=> "xn--wgv71a119e.jp"
SimpleIDN.to_unicode("xn--wgv71a119e.jp") #=> "日本語.jp" 

どこまで信頼できるかはよくわからないが、手っ取り早く確かめたい時に便利。

objcopyでバイナリにデータを埋め込む

リンカ・ローダ実践開発テクニック―実行ファイルを作成するために必須の技術 (COMPUTER TECHNOLOGY)に載っていた方法。

objcopyを使えばファイルのデータをELFのセクションとして埋め込むことができる。

以下のコード(binary.c)ではbindataセクションにデータを埋め込むことを想定しており、そのアドレスを__start_bindataによって参照している。

#include <stdio.h>

extern char __start_bindata;
extern char __stop_bindata;

int main()
{
  char *p;

  printf("__start_bindata = 0x%08x\n", (int)(&__start_bindata));
  printf("__stop_bindata = 0x%08x\n", (int)(&__stop_bindata));

  for (p = &__start_bindata; p < &__stop_bindata; p++) {
    putchar(*p);
  }

  return 0;
}

埋め込みたいデータを用意。

$ echo "Hello World" > binary.dat

オブジェクトファイルを作成。

$ gcc -c binary.c -Wall

bindataセクションにbinary.datを埋め込んだオブジェクトファイルbinary_tmp.oを生成。

$ objcopy --add-section=bindata=binary.dat \
    --set-section-flags=bindata=contents,alloc,load,readonly,data binary.o binary_tmp.o

bindataというセクションができている。

$ readelf -W -S binary_tmp.o
There are 14 section headers, starting at offset 0x1c8:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        0000000000000000 000040 00006f 00  AX  0   0  4
  [ 2] .rela.text        RELA            0000000000000000 0006f0 0000d8 18     12   1  8
  [ 3] .data             PROGBITS        0000000000000000 0000b0 000000 00  WA  0   0  4
  [ 4] .bss              NOBITS          0000000000000000 0000b0 000000 00  WA  0   0  4
  [ 5] .rodata           PROGBITS        0000000000000000 0000b0 000033 00   A  0   0  1
  [ 6] .comment          PROGBITS        0000000000000000 0000e3 00002e 01  MS  0   0  1
  [ 7] .note.GNU-stack   PROGBITS        0000000000000000 000111 000000 00      0   0  1
  [ 8] .eh_frame         PROGBITS        0000000000000000 000118 000038 00   A  0   0  8
  [ 9] .rela.eh_frame    RELA            0000000000000000 0007c8 000018 18     12   8  8
  [10] bindata           PROGBITS        0000000000000000 000150 00000c 00   A  0   0  1
  [11] .shstrtab         STRTAB          0000000000000000 00015c 000069 00      0   0  1
  [12] .symtab           SYMTAB          0000000000000000 000548 000168 18     13  10  8
  [13] .strtab           STRTAB          0000000000000000 0006b0 00003d 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

bindataセクションのオフセットは0x150となっているのでそこを見てみる。

$ hexdump -C binary_tmp.o
(略)
00000140  00 41 0e 10 86 02 43 0d  06 02 6a 0c 07 08 00 00  |.A....C...j.....|
00000150  48 65 6c 6c 6f 20 57 6f  72 6c 64 0a 00 2e 73 79  |Hello World...sy|
00000160  6d 74 61 62 00 2e 73 74  72 74 61 62 00 2e 73 68  |mtab..strtab..sh|
00000170  73 74 72 74 61 62 00 2e  72 65 6c 61 2e 74 65 78  |strtab..rela.tex|
(略)

"Hello World"が含まれている。

最後にデータを埋め込んだオブジェクトファイルから実行形式を作成。

$ gcc binary_tmp.o -o binary
$ ./binary
__start_bindata = 0x004006bb
__stop_bindata = 0x004006c7
Hello World

これを利用して関数を埋め込んでそれを呼び出すということをやってみる。

以下の関数を埋め込む。(func.c)

int func(int a, int b)
{
  return a + b;
}

オブジェクトファイルを作成。

$ gcc -c func.c

セクション情報から.textセクションはオフセット0x40から始まっていることがわかる。

$ readelf -W -S func.o
There are 11 section headers, starting at offset 0x118:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        0000000000000000 000040 000011 00  AX  0   0  4
  [ 2] .data             PROGBITS        0000000000000000 000054 000000 00  WA  0   0  4
  [ 3] .bss              NOBITS          0000000000000000 000054 000000 00  WA  0   0  4
  [ 4] .comment          PROGBITS        0000000000000000 000054 00002e 01  MS  0   0  1
  [ 5] .note.GNU-stack   PROGBITS        0000000000000000 000082 000000 00      0   0  1
  [ 6] .eh_frame         PROGBITS        0000000000000000 000088 000038 00   A  0   0  8
  [ 7] .rela.eh_frame    RELA            0000000000000000 0004c0 000018 18      9   6  8
  [ 8] .shstrtab         STRTAB          0000000000000000 0000c0 000054 00      0   0  1
  [ 9] .symtab           SYMTAB          0000000000000000 0003d8 0000d8 18     10   8  8
  [10] .strtab           STRTAB          0000000000000000 0004b0 00000c 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

関数の機械語は以下のようになっていて、

$ objdump -d func.o

func.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <sum>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 7d fc                mov    %edi,-0x4(%rbp)
   7:   89 75 f8                mov    %esi,-0x8(%rbp)
   a:   b8 61 1e 00 00          mov    $0x1e61,%eax
   f:   c9                      leaveq
  10:   c3                      retq

これは以下から、実際にオフセット0x40の位置にあることがわかる。

00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  01 00 3e 00 01 00 00 00  00 00 00 00 00 00 00 00  |..>.............|
00000020  00 00 00 00 00 00 00 00  18 01 00 00 00 00 00 00  |................|
00000030  00 00 00 00 40 00 00 00  00 00 40 00 0b 00 08 00  |....@.....@.....|
00000040  55 48 89 e5 89 7d fc 89  75 f8 b8 61 1e 00 00 c9  |UH...}..u..a....|
00000050  c3 00 00 00 00 47 43 43  3a 20 28 47 4e 55 29 20  |.....GCC: (GNU) |
(略)

そこで以下のように関数ポインタが埋め込み先のセクションの先頭からオフセット0x40の位置を指すようにしておく。

#include <stdio.h>

extern char __start_exec;
extern char __stop_exec;

int main()
{
  int res;
  int (*fp)(int, int);
  fp = &__start_exec + 0x40;

  res = (*fp)(10, 90);
  printf("%d\n", res);

  return 0;
}

さっきと同じようにしてファイルを埋め込む。
ただし、埋め込んだセクションを実行可能にしておく必要がある。

$ gcc -c binary.c -Wall
$ objcopy --add-section=exec=func.o \
    --set-section-flags=exec=contents,alloc,load,readonly,code binary.o binary_tmp.o
$ gcc gcc binary_tmp.o -o binary

以下のように埋め込んだ関数を実行できていることがわかる。

$ ./binary
100

main関数以外から関数を呼び出す

#include <stdio.h>
#include <stdlib.h>

void ctor()
{
  puts("ctor");
}

void dtor()
{
  puts("dtor");
}

void (*fp1)(void) __attribute__((section(".ctors"))) = ctor;
void (*fp2)(void) __attribute__((section(".dtors"))) = dtor;

void __attribute__((constructor)) init()
{
  puts("init");
}

void __attribute__((destructor)) finit()
{
  puts("finit");
}


int main()
{
  puts("main1");
  exit(0);
  puts("main2");
  return 0;
}

普通にコンパイル(GCCで)して実行すると以下のような出力になる。

init
ctor
main1
dtor
finit

C言語での整数型間の変換

C言語の整数型には符号付き・符号なし、ビット列の大きさの2要素の組み合わせから種々の型があり、その間の変換規則にはややこしいものがある。
変換規則をまとめる。

以下の結果はC標準で規定されている規則であるかどうかまでは調べていない。
以下の結果は符号ありのビット表現が2の補数となっている環境下での結果だがC標準では符号ありのビット表現が2の補数であることを要請していない(らしい)。

なお、実行環境

解析のために以下のような関数を定義しておく。
(show_bit関数ではCPUがリトルエンディアンであることを仮定している。)

void show_bit(void *p, int len)
{
  unsigned char *s;
  int i, j;

  s = (unsigned char *)p;
  for (i = len - 1; i >= 0; i--) {
    for (j = 7; j >= 0; j--) {
      printf("%d", (*(s + i) >> j) & 1);
    }
    putc(' ', stdout);
  }
  putc('\n', stdout);
}


void show_short(short x)
{
  printf("%hd\n", x);
  show_bit(&x, sizeof(x));
}

void show_unsigned_short(unsigned short x)
{
  printf("%hu\n", x);
  show_bit(&x, sizeof(x));
}

void show_int(int x)
{
  printf("%d\n", x);
  show_bit(&x, sizeof(x));
}

void show_unsigned_int(unsigned int x)
{
  printf("%u\n", x);
  show_bit(&x, sizeof(x));
}

同一サイズ間の変換

例えば、int -> unsigned intunsigned short -> shortのような変換。

  • ビット列:変換前と変換後で同一
  • 表現される数値:変換前と変換後で変わり得る
  short x = 0x7fffU;
  unsigned short y = x;

  show_short(x);
  show_unsigned_short(y);
  show_unsigned_short(++y);
  x = y;
  show_short(x);

結果は

32767
01111111 11111111
32767
01111111 11111111
32768
10000000 00000000
-32768
10000000 00000000

サイズの拡大

符号なしから符号なしへの拡大

例えば、unsigned short -> unsigned intのような変換。

  • ビット列:拡大前のビット列の先頭に0のビットを付加したものが拡大後のビット列(zero extension)
  • 表現される数値:拡大前と拡大後で同一
  unsigned short sx = 0x0fffU;
  unsigned short sy = 0xf000U;
  unsigned int x = sx;
  unsigned int y = sy;

  show_unsigned_short(sx);
  show_unsigned_short(sy);
  show_unsigned_int(x);
  show_unsigned_int(y);

結果は

4095
00001111 11111111
61440
11110000 00000000
4095
00000000 00000000 00001111 11111111
61440
00000000 00000000 11110000 00000000

拡大後の型が表現する数値の範囲が拡大前の型が表現する数値の範囲を含んでいるので変なことは起こらない。

符号ありから符号ありへの拡大

例えば、short -> intのような変換。

  • ビット列:拡大前のビット列の先頭に拡大前のビット列の先頭ビット付加したものが拡大後のビット列(sign extension)
  • 表現される数値:拡大前・拡大後で同一
  short sx = 0x0fff;
  short sy = 0xf000;
  int x = sx;
  int y = sy;

  show_short(sx);
  show_short(sy);
  show_int(x);
  show_int(y);

結果は

4095
00001111 11111111
-4096
11110000 00000000
4095
00000000 00000000 00001111 11111111
-4096
11111111 11111111 11110000 00000000

これも、拡大後の型が表現する数値の範囲が拡大前の型が表現する数値の範囲を含んでいるので変なことは起こらない。

符号なしから符号ありへの拡大

例えば、unsigned short -> intのような変換。

  • ビット列:拡大前のビット列の先頭に0のビットを付加したものが拡大後のビット列(zero extension)
  • 表現される数値:拡大前・拡大後で同一
  unsigned short sx = 0x0fffU;
  unsigned short sy = 0xf000U;
  int x = sx;
  int y = sy;

  show_unsigned_short(sx);
  show_unsigned_short(sy);
  show_int(x);
  show_int(y);

結果は

4095
00001111 11111111
61440
11110000 00000000
4095
00000000 00000000 00001111 11111111
61440
00000000 00000000 11110000 00000000

これも、拡大後の型が表現する数値の範囲が拡大前の型が表現する数値の範囲を含んでいるので変なことは起こらない。

符号ありから符号なしへの拡大

例えば、short -> unsigned intのような変換。

  • ビット列:拡大前のビット列の先頭に拡大前のビット列の先頭ビット付加したものが拡大後のビット列(sign extension)
  • 表現される数値:拡大前・拡大後で変わり得る
  short sx = 0x0fff;
  short sy = 0xf000;
  unsigned int x = sx;
  unsigned int y = sy;

  show_short(sx);
  show_short(sy);
  show_unsigned_int(x);
  show_unsigned_int(y);
  show_unsigned_int((unsigned int)(int)sy);
  show_unsigned_int((unsigned int)(unsigned short)sy);

結果は

4095
00001111 11111111
-4096
11110000 00000000
4095
00000000 00000000 00001111 11111111
4294963200
11111111 11111111 11110000 00000000
4294963200
11111111 11111111 11110000 00000000
61440
00000000 00000000 11110000 00000000

結果から、先にサイズを変換し、その後、符号あり・符号なしを変換していることがわかる。(short -> int -> unsigned int)

これは変なことが起こる。

サイズの縮小

符号あり・符号なしに関わらず、

  • ビット列:縮小前のビット列の先頭のビットを切り捨てたものが縮小後のビット列
  • 表現される数値:縮小前と縮小後で変わり得る
  int x = 0x0fffffff;
  int y = 0xffffffff;
  short sx = x;
  unsigned short sy = y;

  show_int(x);
  show_int(y);
  show_short(sx);
  show_unsigned_short(sy);

結果は

268435455
00001111 11111111 11111111 11111111
-1
11111111 11111111 11111111 11111111
-1
11111111 11111111
65535
11111111 11111111

ELF のセクション名一覧を表示

ELF形式のファイルには先頭にELFヘッダーがある。
ELFヘッダーは以下の構造体になっている。

typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf64_Half    e_type;                 /* Object file type */
  Elf64_Half    e_machine;              /* Architecture */
  Elf64_Word    e_version;              /* Object file version */
  Elf64_Addr    e_entry;                /* Entry point virtual address */
  Elf64_Off     e_phoff;                /* Program header table file offset */
  Elf64_Off     e_shoff;                /* Section header table file offset */
  Elf64_Word    e_flags;                /* Processor-specific flags */
  Elf64_Half    e_ehsize;               /* ELF header size in bytes */
  Elf64_Half    e_phentsize;            /* Program header table entry size */
  Elf64_Half    e_phnum;                /* Program header table entry count */
  Elf64_Half    e_shentsize;            /* Section header table entry size */
  Elf64_Half    e_shnum;                /* Section header table entry count */
  Elf64_Half    e_shstrndx;             /* Section header string table index */
} Elf64_Ehdr;

先頭からe_shoff行った所からセクションヘッダーが並んでいる。
セクションの名前はセクションヘッダーではなく.shstrtabセクションに含まれている。 .shstrtabセクションのセクションヘッダーはELFヘッダーのe_shstrndxが示す位置に並んでいる。 セクションヘッダーのサイズはELFヘッダーのe_shentsizeから得られる。
なので、.shstrtabセクションの開始オフセットは
e_shoff + e_shentsize * e_shstrndx のように計算できる。

セクションヘッダーは以下の構造体になっている。

typedef struct
{
  Elf64_Word    sh_name;                /* Section name (string tbl index) */
  Elf64_Word    sh_type;                /* Section type */
  Elf64_Xword   sh_flags;               /* Section flags */
  Elf64_Addr    sh_addr;                /* Section virtual addr at execution */
  Elf64_Off     sh_offset;              /* Section file offset */
  Elf64_Xword   sh_size;                /* Section size in bytes */
  Elf64_Word    sh_link;                /* Link to another section */
  Elf64_Word    sh_info;                /* Additional section information */
  Elf64_Xword   sh_addralign;           /* Section alignment */
  Elf64_Xword   sh_entsize;             /* Entry size if section holds table */
} Elf64_Shdr;

.shstrtabはNULL文字終端の文字列からなっており、sh_nameがそのセクションの名前が.shstrtab中の何番目にあるのかを示している。

なので以下のようなプログラム(read_section.c)でセクション名一覧を表示できる。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <elf.h>
#include <sys/stat.h>
#include <sys/mman.h>

int is_elf(Elf64_Ehdr *ehdr)
{
  return
    ehdr->e_ident[EI_MAG0] == ELFMAG0 &&
    ehdr->e_ident[EI_MAG1] == ELFMAG1 &&
    ehdr->e_ident[EI_MAG2] == ELFMAG2 &&
    ehdr->e_ident[EI_MAG3] == ELFMAG3;
}



int main(int argc, char **argv)
{
  int fd, i;
  struct stat sb;
  char *head;
  Elf64_Ehdr *ehdr;
  Elf64_Shdr *shdr, *shstr;

  fd = open(argv[1], O_RDONLY);
  fstat(fd, &sb);
  head = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);

  ehdr = (Elf64_Ehdr *)head;

  if (!is_elf(ehdr)) {
    fprintf(stderr, "%s is not elf\n", argv[1]);
    exit(1);
  }
  shstr = (Elf64_Shdr *)(head + ehdr->e_shoff
                              + ehdr->e_shentsize * ehdr->e_shstrndx);
  for (i = 0; i < ehdr->e_shnum; i++) {
    shdr = (Elf64_Shdr *)(head + ehdr->e_shoff + ehdr->e_shentsize * i);
    printf("%s\n", (char *)(head + shstr->sh_offset + shdr->sh_name));
  }

  return 0;
}

コンパイルして実行。

$ gcc read_section.c
$ ./a.out a.out

.interp
.note.ABI-tag
.note.gnu.build-id
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.ctors
.dtors
.jcr
.dynamic
.got
.got.plt
.data
.bss
.comment
.shstrtab
.symtab
.strtab

readelf で折り返さず表示

-Wオプションを付ける。

オプションなし

$ readelf -aW a.out
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4003e0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          2488 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 27

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
(略)

オプションあり

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4003e0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          2488 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 27

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000400200 000200 00001c 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            000000000040021c 00021c 000020 00   A  0   0  4
  [ 3] .note.gnu.build-id NOTE            000000000040023c 00023c 000024 00   A  0   0  4
(略)

(ブログが狭くてはみ出てる…)

はじめて読む8086

GW中に「はじめて読む8086」という本を読んだ。

8086というのはインテルが開発、発売していたCPU「Intel 8086」のことで、今まで連綿と続くx86アーキテクチャを採用した最初のCPU。 ただし、8086で採用されたx86は16ビット版のx86

8086(x86-16)のアセンプラについてアセンプラを理解するのに必要なCPUやメモリ、I/Oの仕組みと合わせてわかりやすく解説されている。 開発環境はMS-DOSを想定しているが、そこら辺は気にする必要はない。

以下、メモ。

8086のレジスタ一覧

汎用レジスタ

インデックスレジスタ

  • SI(ソースインデックス)
  • DI(デスティネーションインデックス)

特殊レジスタ

  • BP(ベースポインタ)
  • SP(スタックポインタ)
  • IP(インストラクションポインタ)

セグメントレジスタ

  • CS(コードセグメント)
  • DS(データセグメント)
  • ES(エクストラセグメント)
  • SS(スタックセグメント)

あと、フラグレジスタという演算結果の状態を記録するレジスタがある。

どのレジスタもサイズは16ビット。
汎用レジスタは上位バイトと下位バイトを分けることにより8ビットのレジスタとしても使うことができる。

メモリやI/Oとのやり取り

8086ではアドレスバスが20本ある。 つまり、8086で扱えるメモリは1MiB(=2**20バイト)。

8086は20本あるアドレスバスの下位16本を利用してI/Oアクセスを行う。 つまり、8086で扱えるI/Oポートは65536(=2**16)個。

メモリへのアクセスなのか、I/Oへのアクセスなのかはアドレスバスとは別に信号を送って区別する。 CPUによってはアクセスするアドレスによってメモリへのアクセスなのかI/Oへのアクセスなのかを区別する(メモリマップドI/O)。

セグメント方式

8086ではメモリ管理にセグメント方式という方法をとっている。

メモリ全体はいくつかのセグメント(領域)に分けて管理される。
セグメントの始まり(セグメントベース)の物理アドレスはどこでもいいわけではなくて16の倍数でなくてはならない。 つまり物理アドレスを16進数20ビットで表したときに一番下の桁が0になっていないといけない。
またセグメントは連続していなければならない。 これは、例えば、一つのセグメントが0x00000から0x00010までと0x00040から0x00100まで、みたいに分かれていてはいけないということを意味する。

セグメントベースの物理アドレスから下位4ビット(セグメントの制約からこれはどれも0)を除いた16ビットの数値をセグメントアドレスという。

アドレスの指定はセグメントベースとセグメントベースを起点としたオフセットによって行う。 オフセットはオフセットアドレスという16ビットの数値で表される。 これはつまり、オフセットの最大値は0xffffで1つのセグメントの最大の大きさも0xffffであることを意味する。

まとめると、セグメント方式では、セグメントアドレスが0xffe1のセグメントのオフセットアドレスが0x0024のメモリへのアクセスは 物理アドレス0xffe34(= 0xffe10 + 0x0024)へのアクセスに変換される。

この変換はCPUによって行われ、アセンブラでの通常のアドレス指定はオフセットアドレスで行う。 セグメントアドレスは用途によってセグメントアドレス郡に格納されているどれかの値をセグメントアドレスとして使う。

はじめて読む8086―16ビット・コンピュータをやさしく語る (アスキーブックス)

はじめて読む8086―16ビット・コンピュータをやさしく語る (アスキーブックス)