Programmer's Note

コード読み書きの備忘録。

ARMボードエミュ:Bare MetalでHello World

前にARMのbare metal programmingでタスクスイッチっぽいところまで作ったけど、また似たようなことをやりたくなった。 とりあえず、もう一回初めからメモ。

参照記事

Emulating ARM PL011 serial ports | Freedom Embedded

ソースコード

ほぼ参照記事の流用だけど。

arm_bare_metal_programming/00_hello_world at main · yenshan/arm_bare_metal_programming · GitHub

Mac OSでarmのクロスコンパイラのインストール

$ brew install arm-none-eabi-gcc
$ brew install arm-none-eabi-gdb

ビルド&実行

$ make
/opt/homebrew/bin/arm-none-eabi-gcc -c -Wall -nostdinc -nostdlib -fno-builtin -I. test.c
/opt/homebrew/bin/arm-none-eabi-gcc -c -Wall -nostdinc -nostdlib -fno-builtin -I. startup.s
/opt/homebrew/bin/arm-none-eabi-gcc -c -Wall -nostdinc -nostdlib -fno-builtin -I. serial.c
/opt/homebrew/bin/arm-none-eabi-gcc test.o startup.o serial.o -o test -Wall -nostdinc -nostdlib -fno-builtin -I. -static -T ld.scr -L.
/opt/homebrew/opt/arm-none-eabi-binutils/bin/arm-none-eabi-ld: warning: test has a LOAD segment with RWX permissions
cp test test.elf
/opt/homebrew/bin/arm-none-eabi-strip test
/opt/homebrew/bin/arm-none-eabi-objcopy -O binary test test.bin
$ ./run.sh
Hello, World!!!

表示されたのはいいが、QEMUから抜けるにはどうすれば良いのか。 とりあえず、ps ID調べてkillした。

QEMUと実機の違い

この記事によると、 Hello world for bare metal ARM using QEMU | Freedom Embedded

QEMULinuxのブートに特化していて、本来のARMのブートシーケンスのエミュレーションを端折っている。 0x00010000 にカーネルイメージをロードして実行する仕様。 なので自作のプログラムも0x00010000にロードされる前提で作る必要がある。 主に ld.scr の内容が影響される。 あと、割り込みベクターテーブルの設定もできないっぽい。

そこらへんも bare metalしたいととなると、実機しかないよな。

M1 MacのUTMでNet BSD 10.0 を動かす

下記サイトの手順をそのまま実行して、うまくいった。

Advanced Programming in the UNIX Environment

実際にNetBSDを使う場合に、わざわざX-WIndowを導入する必要はなく、 Mac OSからsshでログインして使えばいいという発想が自分には新鮮だった。

その方が軽いし、キーボードの設定をしなくて済むし、Terminal Windowを同時にいくつ開いてもOKなわけだ。 もともとtmuxを使っていたので、Windowを複数開く必要はないけど。

NetBSDで何をやろうか?は特に思いつかないけど、 ビルドしてgdbで遊ぶくらいか。

とりあえず、od -c . でディレクトリをダンプしたら、ちゃんとUNIX V6, V7のように出力されて、感動。

hys$ od -c .
0000000  207 213 004  \0  \f  \0 004 001   .  \0  \0  \0 201 213 004  \0
0000020   \f  \0 004 002   .   .  \0  \0 210 213 004  \0 020  \0  \b 007
0000040    h   e   l   l   o   .   c  \0 211 213 004  \0 330 001  \b 005
0000060    a   .   o   u   t  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000100   \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0001000

新しいスタイルの等幅フォント技術:Texture Healing

中嶋聡さんのメルマガの読者の質問にてTexture Healingを知る。 面白そう。 好きフォントは良きキーボードと同じ、作業のモチベーションが上がるからなあ。 ちょっと試してみたい。

Texture Healingとは何か?

以下、Feloに質問した回答の抜粋。

Texture Healing(テクスチャヒーリング)とは、主にモノスペースフォント(等幅フォント)のデザインにおいて、文字間の「密度」や「空気感」を調整するための新しい技術や概念を指します。この技術は、モノスペースフォントの持つ視覚的な不均衡を解消し、より自然で読みやすい見た目を実現することを目的としています。

背景と課題

モノスペースフォントでは、すべての文字が同じ幅で描かれるため、文字の形状によっては以下のような問題が発生します: - 狭い文字が広すぎる問題
例えば「i」や「l」のような細い文字は、等幅フォントでは不自然に広く見えることがあります。 - 広い文字が狭すぎる問題
一方で「m」や「w」のような幅広い文字は、等幅に収めるために窮屈に見えることがあります。

これらの問題により、単語や文章全体の「密度」や「空気感」が不均一になり、視覚的なバランスが崩れることがあります。

わかりやすい絵がある解説

github.com

PDP-11エミュでUNIX V7を動かす

PDP-11のUNIX V7のdiskイメージの入手サイト

https://simh.trailing-edge.com/software.html

ダウンロードファイルは uv7swre.zip。 解凍すると

AncientUnix.pdf
README.txt
unix_v7_rl.dsk

が入っている。

起動手順

$ pdp11

PDP-11 simulator V3.12-5
sim> set cpu 11/45
Disabling XQ
sim> set cpu 256k
sim> set rl0 RL02
sim> att rl0 unix_v7_rl.dsk
sim> boot rl0
@boot
New Boot, known devices are hp ht rk rl rp tm vt
: rl(0,0)rl2unix
mem = 177856

^D を押す

# Restricted rights: Use, duplication, or disclosure
is subject to restrictions stated in your contract with
Western Electric Company, Inc.
Thu Sep 22 05:49:39 EDT 1988

login: root
Password: ※root
You have mail.
# 

ディレクトリ情報をダンプする

UNIX V7の時代は、ディレクトリに対してodすると、中身を見ることができた。 それを試してみる。

bin
boot
dev
etc
hphtunix
hptmunix
lib
mdec
rkunix
rl2unix
rphtunix
rptmunix
tmp
usr
# od -c .
0000000 002  \0   .  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000020 002  \0   .   .  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000040   f  \0   b   i   n  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000060 224  \0   b   o   o   t  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000100 223  \0   d   e   v  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000120 221  \0   e   t   c  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000140   }  \0   r   l   2   u   n   i   x  \0  \0  \0  \0  \0  \0  \0
0000160   |  \0   r   k   u   n   i   x  \0  \0  \0  \0  \0  \0  \0  \0
0000200   {  \0   l   i   b  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000220   h  \0   h   p   h   t   u   n   i   x  \0  \0  \0  \0  \0  \0
0000240   g  \0   h   p   t   m   u   n   i   x  \0  \0  \0  \0  \0  \0
0000260   . 001   u   s   r  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000300 256 007   m   d   e   c  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000320 251 007   r   p   h   t   u   n   i   x  \0  \0  \0  \0  \0  \0
0000340 250 007   r   p   t   m   u   n   i   x  \0  \0  \0  \0  \0  \0
0000360 200  \0   t   m   p  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000400

PDP-11のクロスコンパイラ環境を整える(Mac OS 14.6.1)

久々個人的にUNIX熱が来て、PDP-11エミュレータを動かしたくなった。

最新のgccのクロスコンパイラ環境で整える。

texinfoパッケージインストール

binutilビルドに必要

$ brew install texinfo

GNUのクロスコンパイル環境ビルド&インストール

binutils

#!/bin/sh

curl -O http://www.ring.gr.jp/archives/GNU/binutils/binutils-2.43.tar.bz2

tar xfvz binutils-2.43.tar.bz2

cd binutils-2.43

mkdir build
cd build

../configure --target=pdp11-aout --disable-nls --prefix=/usr/local/pdp11-aout
make && sudo make install

gmp

#!/bin/sh

curl -O http://www.ring.gr.jp/archives/GNU/gmp/gmp-6.3.0.tar.bz2

tar xfvz gmp-6.3.0.tar.bz2

cd gmp-6.3.0

mkdir build
cd build

../configure --prefix=/usr/local/gmp
make && sudo make install

gmfr

#!/bin/sh

curl -O http://www.ring.gr.jp/archives/GNU/mpfr/mpfr-4.2.1.tar.bz2

tar xfvz mpfr-4.2.1.tar.bz2

cd mpfr-4.2.1

mkdir build
cd build

../configure --prefix=/usr/local/mpfr --with-gmp=/usr/local/gmp
make && sudo make install

mpc

#!/bin/sh

curl -O http://www.ring.gr.jp/archives/GNU/mpc/mpc-1.3.1.tar.gz

tar xfvz mpc-1.3.1.tar.gz

cd mpc-1.3.1

mkdir build
cd build

../configure --prefix=/usr/local/mpc --with-gmp=/usr/local/gmp --with-mpfr=/usr/local/mpfr
make && sudo make install

gcc

#!/bin/sh

curl -O http://www.ring.gr.jp/archives/GNU/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz

tar xfvz gcc-14.2.0.tar.gz

cd gcc-14.2.0

mkdir build
cd build

../configure \
        --target=pdp11-aout \
        --disable-nls \
        --disable-threads \
        --disable-shared \
        --disable-libssp \
        --enable-languages=c \
        --with-gmp=/usr/local/gmp \
        --with-mpfr=/usr/local/mpfr \
        --with-mpc=/usr/local/mpc \
        --prefix=/usr/local/pdp11-aout

make && sudo make install

simhのビルド&インストール

下記HPからソースをダウンロード。 http://simh.trailing-edge.com

$ cd sim/ $ make

*BIN/* の下の成果物を /usr/local/simh/ などにコピーする

PATHを追加する

/usr/local/pdp11-aout/bin
/usr/local/simh

malloc動作解析方法メモ

jemallocを使って検査

LD_PRELOAD=/path/to/libjemalloc.so ./your_program

jemallocのデバッグモードを利用して、一般的なメモリ破壊(オーバーフローや不正解放など)を検出します。

環境変数MALLOC_CONFを設定します:

export MALLOC_CONF="abort:true,junk:true,zero:true"
  • abort:true: 異常が検出された際にプログラムを終了。
  • junk:true: 確保されたメモリを特定のパターンで埋める(デバッグ用)。
  • zero:true: 解放後のメモリをゼロクリアする。

チャンク周辺にガードページを追加して、不正なメモリアクセスを検出します。

export MALLOC_CONF="redzone:true"

LD_PRELOADを使用してmallocやfreeの挙動をフックする

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

static void* (*real_malloc)(size_t) = NULL;
static void (*real_free)(void*) = NULL;

void* malloc(size_t size) {
    if (!real_malloc) {
        real_malloc = dlsym(RTLD_NEXT, "malloc");
    }
    void* ptr = real_malloc(size);
    fprintf(stderr, "malloc(%zu) = %p\n", size, ptr);
    return ptr;
}

void free(void* ptr) {
    if (!real_free) {
        real_free = dlsym(RTLD_NEXT, "free");
    }
    fprintf(stderr, "free(%p)\n", ptr);
    real_free(ptr);
}

malloc, free のログの取り方

(Chat GPTさま回答)

SystemTapが利用できない環境で、_int_malloc_int_freeのログを取得したいが、コンソールへの出力によるパフォーマンス低下を避けたいということですね。その場合、カスタムglibcをビルドし、パフォーマンスへの影響を最小限に抑えつつログを取得する方法があります。

以下に、その手順とポイントを詳しく説明します。


1. カスタムglibcのビルド

手順:

  1. glibcソースコードを取得
   # ソースコードを取得
   git clone git://sourceware.org/git/glibc.git
   cd glibc
  1. _int_malloc_int_freeにログコードを追加

    malloc/malloc.c内の該当関数に、ログを記録するコードを挿入します。

   // malloc/malloc.c

   // 必要なヘッダを追加
   #include <sys/syscall.h>
   #include <unistd.h>
   #include <sched.h>
   #include <time.h>

   // ログ用のデータ構造と変数を定義
   #define LOG_BUFFER_SIZE 10240

   typedef struct {
       void* arena;
       size_t size_or_ptr;
       pid_t tid;
       int cpu;
       struct timespec timestamp;
       char operation; // 'M' for malloc, 'F' for free
   } log_entry_t;

   static log_entry_t log_buffer[LOG_BUFFER_SIZE];
   static volatile int log_index = 0;
  1. ログ関数の実装
   // 非ブロッキングでログを記録する関数
   static inline void log_memory_operation(char operation, mstate av, size_t size_or_ptr) {
       int index = __atomic_fetch_add(&log_index, 1, __ATOMIC_RELAXED) % LOG_BUFFER_SIZE;

       log_entry_t* entry = &log_buffer[index];
       entry->operation = operation;
       entry->arena = av;
       entry->size_or_ptr = size_or_ptr;
       entry->tid = syscall(SYS_gettid);
       entry->cpu = sched_getcpu();
       clock_gettime(CLOCK_REALTIME, &entry->timestamp);
   }
  • ポイント:

    • __atomic_fetch_addを使用して、ログインデックスをアトミックに更新し、ロックを回避します。
    • ログバッファはリングバッファとして機能し、古いデータは上書きされます。
  • _int_malloc_int_freeへのログ関数の呼び出し

   // _int_malloc関数内の適切な場所で
   _int_malloc(mstate av, size_t bytes) {
       // 既存のコード...

       // ログを記録
       log_memory_operation('M', av, bytes);

       // 既存のコード...
   }

   // _int_free関数内の適切な場所で
   _int_free(mstate av, mchunkptr p, int have_lock) {
       // 既存のコード...

       // ログを記録
       log_memory_operation('F', av, (size_t)p);

       // 既存のコード...
   }
  1. ログデータの書き出し

    プログラムの終了時や特定のタイミングで、ログバッファの内容をファイルに書き出します。これには、atexit関数やシグナルハンドラを使用できます。

   #include <signal.h>

   void write_log_to_file() {
       FILE *fp = fopen("/path/to/logfile.log", "w");
       if (fp == NULL) return;

       int entries = (log_index < LOG_BUFFER_SIZE) ? log_index : LOG_BUFFER_SIZE;
       for (int i = 0; i < entries; i++) {
           log_entry_t *entry = &log_buffer[i];
           fprintf(fp, "%c, Time: %ld.%09ld, CPU: %d, TID: %d, Arena: %p, Size/Ptr: %zu\n",
                   entry->operation,
                   entry->timestamp.tv_sec, entry->timestamp.tv_nsec,
                   entry->cpu, entry->tid, entry->arena, entry->size_or_ptr);
       }

       fclose(fp);
   }

   // プログラム終了時にログを書き出す
   __attribute__((destructor))
   void on_exit() {
       write_log_to_file();
   }

   // シグナルハンドラの設定(必要に応じて)
   void setup_signal_handler() {
       signal(SIGINT, on_exit);
       signal(SIGTERM, on_exit);
   }
  • ポイント:

    • __attribute__((destructor))を使用して、プログラム終了時に自動的にログを書き出します。
    • シグナルハンドラを設定することで、異常終了時にもログを取得できます。
  • カスタムglibcのビルドとインストール

   mkdir build
   cd build
   ../configure --prefix=/opt/glibc-custom
   make -j$(nproc)
   sudo make install
  1. アプリケーションの実行時にカスタムglibcを使用
   export LD_LIBRARY_PATH=/opt/glibc-custom/lib:$LD_LIBRARY_PATH
   ./your_application

2. パフォーマンスへの影響を最小限に抑える方法

  • ロックフリーのデータ構造の使用

    • アトミック操作を活用して、ロックによるオーバーヘッドを削減します。
    • 上記の例では、__atomic_fetch_addを使用しています。
  • ログバッファのサイズを大きく設定

  • ログのサンプリング

    • 全ての呼び出しを記録せず、一定の割合でサンプリングします。
  // サンプリング率を設定(例: 10%)
  #define LOG_SAMPLING_RATE 10

  static inline void log_memory_operation(char operation, mstate av, size_t size_or_ptr) {
      if (rand() % 100 < LOG_SAMPLING_RATE) {
          // ログを記録
          // 既存のコード...
      }
  }
  • 軽量なロギング

    • フォーマット変換や文字列操作を最小限に抑えます。
    • 必要最低限の情報のみを記録します。

3. 代替案としてのトレーシングツールの利用

LTTng (Linux Trace Toolkit Next Generation)

  • 概要:

    • ユーザ空間とカーネル空間の両方で低オーバーヘッドなトレーシングを提供します。
    • glibcにはlttng-ustというユーザ空間トレーシングが可能なライブラリがあります。
  • 手順:

    1. lttng-ustのインストール

      sudo apt-get install lttng-tools lttng-modules-dkms lttng-modules-utils liblttng-ust-dev
      
    2. glibcにトレースポイントを追加

      • glibcソースコードで、_int_malloc_int_freeにトレースポイントを挿入します。
      #include <lttng/tracepoint.h>
      
      // トレースポイントの定義
      tracepoint(lttng_ust_glibc, int_malloc, av, bytes);
      
      // _int_malloc関数内で
      tracepoint(lttng_ust_glibc, int_malloc, av, bytes);
      
      • トレースポイントのヘッダと実装を作成します。
    3. カスタムglibcのビルド

      • 上記と同様にビルドします。
    4. トレースの開始と停止

      lttng create my-trace
      lttng enable-event -u lttng_ust_glibc:int_malloc
      lttng start
      ./your_application
      lttng stop
      lttng view
      
  • メリット:

    • ログの記録によるオーバーヘッドが非常に低い。
    • 詳細なトレースが可能。
  • デメリット:

    • 初期設定が複雑。
    • カスタムglibcのビルドが必要。

4. まとめ

  • カスタムglibcのビルドにより、_int_malloc_int_freeのログを取得できます。
  • パフォーマンスへの影響を最小限に抑えるために、ロックフリーのデータ構造やサンプリングを活用します。
  • LTTngなどのトレーシングツールを利用することで、さらに低オーバーヘッドなログ取得が可能です。

重要な注意点:

  • カスタムglibcのビルドと適用は、システム全体に影響を及ぼす可能性があるため、十分なテストと検証を行ってください。
  • ログバッファのサイズや記録方法は、システムのリソースとパフォーマンスを考慮して設定してください。
  • セキュリティや安定性の観点から、本番環境での使用は慎重に検討してください。

以上の方法で、コンソールへの直接出力によるパフォーマンス低下を避けながら、必要なログを取得することができます。問題の原因特定にお役立てください。