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
QEMUはLinuxのブートに特化していて、本来の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」のような幅広い文字は、等幅に収めるために窮屈に見えることがあります。
これらの問題により、単語や文章全体の「密度」や「空気感」が不均一になり、視覚的なバランスが崩れることがあります。
わかりやすい絵がある解説
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エミュレータを動かしたくなった。
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のデバッグモードを利用して、一般的なメモリ破壊(オーバーフローや不正解放など)を検出します。
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のビルド
手順:
# ソースコードを取得
git clone git://sourceware.org/git/glibc.git
cd glibc
_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;
- ログ関数の実装
// 非ブロッキングでログを記録する関数
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);
// 既存のコード...
}
ログデータの書き出し
プログラムの終了時や特定のタイミングで、ログバッファの内容をファイルに書き出します。これには、
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
- アプリケーションの実行時にカスタム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というユーザ空間トレーシングが可能なライブラリがあります。
手順:
lttng-ustのインストール
sudo apt-get install lttng-tools lttng-modules-dkms lttng-modules-utils liblttng-ust-devglibcにトレースポイントを追加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);- トレースポイントのヘッダと実装を作成します。
カスタムglibcのビルド
- 上記と同様にビルドします。
トレースの開始と停止
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のビルドと適用は、システム全体に影響を及ぼす可能性があるため、十分なテストと検証を行ってください。 - ログバッファのサイズや記録方法は、システムのリソースとパフォーマンスを考慮して設定してください。
- セキュリティや安定性の観点から、本番環境での使用は慎重に検討してください。
以上の方法で、コンソールへの直接出力によるパフォーマンス低下を避けながら、必要なログを取得することができます。問題の原因特定にお役立てください。
Qemu ARM 32bitでUbuntu 16を実行する
QemuでRaspberry Piを動かす
以下のサイトでうまくいった。 qiita.com
Ubuntuのdisk image
xenial-server-cloudimg-armhf-disk1.img