目次: GCC
GCCのmemmoveに対する最適化とバグっぽい挙動についてのメモです。半年経ったら絶対忘れてわからなくなる自信があるので、書き残しておきます。
検証に使用するのは下記のプログラムです。"AB0123456789abcdefg" が格納された配列sから、同じ配列sに2文字だけずらしてmemmove() します。期待値は先頭2文字が消えた "0123456789abcdefg" です。このプログラムはmemmove() を使う必要があります。memcpy() はコピー元(src)とコピー先(dst)が重複したメモリ領域だと正常に動作しないことがあるからです。
#include <stdio.h>
#include <string.h>
#define PRE "AB"
#define STR "0123456789abcdefg"
#define NOP __asm__ volatile("nop;")
int main(int argc, char *argv[])
{
volatile char s[] = PRE STR;
char *p = (char *)s;
size_t sz_pre = strlen(PRE);
size_t sz = strlen(p) - sz_pre + 1;
NOP;
memmove(p, p + sz_pre, sz);
NOP; NOP;
if (strcmp(p, STR) == 0) {
printf(" OK: %s\n", p);
} else {
printf(" NG: %s\n", p);
}
}
$ gcc -O2 -Wall -g test.c $ ./a.out NG: 01234567abcdefg
しかし、実行してみると期待値と一致しません。真ん中の "89" がどこかに消えました。また配列sの宣言からvolatileを削除すると、正常に動作するようになります。一体、何が起きているのでしょうか?
GCCはある条件下でmemmove() を __builtin_memcpy() に置き換えるという最適化を行います。その様子を観察するために、オプション --dump-tree-allを付けて、GIMPLEのファイルを出力します。
$ gcc -O2 -Wall -g test.c --dump-tree-all $ grep -r 'memmove ' ./ | sort ./test.c.004t.original: memmove ((void *) p, (const void *) (p + (sizetype) sz_pre), sz); ./test.c.005t.gimple: memmove (p, _3, sz); ./test.c.007t.omplower: memmove (p, _3, sz); ./test.c.008t.lower: memmove (p, _3, sz); ./test.c.011t.eh: memmove (p, _3, sz); ./test.c.013t.cfg: memmove (p, _3, sz); ./test.c.015t.ompexp: memmove (p, _3, sz); ./test.c.020t.fixup_cfg1: memmove (p, _3, sz); ./test.c.021t.ssa: memmove (p_8, _3, sz_10); ./test.c.023t.nothrow: memmove (p_8, _3, sz_10); ./test.c.025t.fixup_cfg2: memmove (p_8, _3, sz_10); ./test.c.026t.local-fnsummary1: memmove (p_8, _3, sz_10); ./test.c.027t.einline: memmove (p_8, _3, sz_10); ./test.c.028t.early_optimizations: memmove (p_8, _3, sz_10); ./test.c.029t.objsz1: memmove (p_8, _3, sz_10);
ファイルをmemmoveで検索すると、029t.objsz1を最後に出現しなくなっています。029t.objsz1の次である030t.cpp1を見て、memmoveがどうなったのか確かめます。
// test.c.030t.ccp1
;; Function main (main, funcdef_no=11, decl_uid=2555, cgraph_uid=12, symbol_order=11)
main (int argc, char * * argv)
{
...
__asm__ __volatile__("nop;");
# DEBUG BEGIN_STMT
__builtin_memcpy (&s, &MEM <volatile char[20]> [(void *)&s + 2B], sz_10);
# DEBUG BEGIN_STMT
__asm__ __volatile__("nop;");
# DEBUG BEGIN_STMT
__asm__ __volatile__("nop;");
...
検証用プログラムではmemmoveと書いたはずの場所が、__builtin_memcpy() に変わっています。この __builtin_memcpy() は最終的にアセンブラに展開されます。あまり詳しく追っていませんが、末尾の余りバイトをコピーしてから、先頭から8バイトずつコピーするループが実行されるようで、srcとdestが重なっていると、正常に動作しません。
$ objdump -drS a.out
...
NOP;
1084: 90 nop
size_t sz = strlen(p) - sz_pre + 1;
1085: 48 8d 50 ff lea -0x1(%rax),%rdx
memmove(p, p + sz_pre, sz);
1089: 48 8d 4c 24 02 lea 0x2(%rsp),%rcx
108e: 48 83 fa 08 cmp $0x8,%rdx
1092: 73 52 jae 10e6 <main+0x86> ★コピーするコードの本体
1094: f6 c2 04 test $0x4,%dl
1097: 0f 85 85 00 00 00 jne 1122 <main+0xc2>
109d: 48 85 d2 test %rdx,%rdx
10a0: 74 0f je 10b1 <main+0x51>
10a2: 0f b6 01 movzbl (%rcx),%eax
10a5: 88 45 00 mov %al,0x0(%rbp)
10a8: f6 c2 02 test $0x2,%dl
10ab: 0f 85 80 00 00 00 jne 1131 <main+0xd1>
NOP; NOP;
10b1: 90 nop
10b2: 90 nop
...
memmove(p, p + sz_pre, sz);
10e6: 48 8b 74 14 fa mov -0x6(%rsp,%rdx,1),%rsi
10eb: 48 83 e8 02 sub $0x2,%rax
10ef: 48 89 74 14 f8 mov %rsi,-0x8(%rsp,%rdx,1)
10f4: 48 83 f8 08 cmp $0x8,%rax
10f8: 72 b7 jb 10b1 <main+0x51>
10fa: 48 83 e0 f8 and $0xfffffffffffffff8,%rax
10fe: 31 d2 xor %edx,%edx
1100: 48 8b 34 11 mov (%rcx,%rdx,1),%rsi ★この辺りがコピーのためのループ
1104: 48 89 74 15 00 mov %rsi,0x0(%rbp,%rdx,1)
1109: 48 83 c2 08 add $0x8,%rdx
110d: 48 39 c2 cmp %rax,%rdx
1110: 72 ee jb 1100 <main+0xa0>
1112: eb 9d jmp 10b1 <main+0x51>
printf(" OK: %s\n", p);
1114: 48 8d 3d fb 0e 00 00 lea 0xefb(%rip),%rdi # 2016 <_IO_stdin_used+0x16>
111b: e8 20 ff ff ff callq 1040 <printf@plt>
1120: eb bc jmp 10de <main+0x7e>
memmove(p, p + sz_pre, sz);
1122: 8b 01 mov (%rcx),%eax
1124: 89 45 00 mov %eax,0x0(%rbp)
1127: 8b 44 11 fc mov -0x4(%rcx,%rdx,1),%eax
112b: 89 44 15 fc mov %eax,-0x4(%rbp,%rdx,1)
112f: eb 80 jmp 10b1 <main+0x51>
1131: 0f b7 44 11 fe movzwl -0x2(%rcx,%rdx,1),%eax
1136: 66 89 44 15 fe mov %ax,-0x2(%rbp,%rdx,1)
113b: e9 71 ff ff ff jmpq 10b1 <main+0x51>
長くなってきたので、また次回。
目次: C言語とlibc
C言語の規格では浮動小数点の丸めモードが4つ定義されています。実行中に変更することができ、fesetround() 関数で指定します。
ついUPWARD = 切り上げ、DOWNWARD = 切り捨て、と説明したくなりますが、TOWARDZEROと区別が付きませんし、正の数はまだしも、負の数を考えたとき混乱します。やや意味はわかりにくいですが、誤解のない説明をすると、こんな感じです。
DOWNWARD, TOWARDZERO, UPWARDは他に解釈の余地がありませんが、TONEARESTはど真ん中の数が来たときに、扱いに困ります。IEEE 754では2つの方式が定義されているようです。
説明するより実際に動かしたほうがわかりやすいでしょう。
丸めモードの動作を確認するプログラムを書きます。8388610という数値はfloatの仮数部24bitを使い切った値となっています。1.0の増減は表現できますが、1.0より小さい値の増減はビットが足りないので表せないです。
8388610に対し1.0より小さい値(今回は0.25, 0.5, 0.75の3つを選びました)を加減算すれば、結果を正確に表現できないので、必ず丸め処理が行われます。丸めモードによる演算結果の違いを見るには丁度よいですね。
#include <stdio.h>
#include <fenv.h>
#include <float.h>
#define BASE_EVEN 8388610.0
#define BASE_ODD 8388611.0
union n_f {
int n;
float f;
};
static inline void test(void)
{
union n_f a, b, c;
a.f = BASE_EVEN;
b.f = 0.25;
c.f = a.f + b.f;
printf(" %.2f+%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
b.f = 0.5;
c.f = a.f + b.f;
printf(" %.2f+%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
b.f = 0.75;
c.f = a.f + b.f;
printf(" %.2f+%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
a.f = BASE_ODD;
b.f = 0.25;
c.f = a.f + b.f;
printf(" %.2f+%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
b.f = 0.5;
c.f = a.f + b.f;
printf(" %.2f+%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
b.f = 0.75;
c.f = a.f + b.f;
printf(" %.2f+%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
a.f = -BASE_EVEN;
b.f = 0.25;
c.f = a.f - b.f;
printf(" %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
b.f = 0.5;
c.f = a.f - b.f;
printf(" %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
b.f = 0.75;
c.f = a.f - b.f;
printf(" %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
a.f = -BASE_ODD;
b.f = 0.25;
c.f = a.f - b.f;
printf(" %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
b.f = 0.5;
c.f = a.f - b.f;
printf(" %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
b.f = 0.75;
c.f = a.f - b.f;
printf(" %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
}
int main(int argc, char *argv[])
{
printf("DOWNWARD\n");
fesetround(FE_DOWNWARD);
test();
printf("TONEAREST\n");
fesetround(FE_TONEAREST);
test();
printf("TOWARDZERO\n");
fesetround(FE_TOWARDZERO);
test();
printf("UPWARD\n");
fesetround(FE_UPWARD);
test();
return 0;
}
ところが、コンパイルして実行すると奇妙な結果が得られます。全てTONEARESTと同じ結果になってしまいます。
$ gcc -Wall -g -O2 a.c -lm $ ./a.out DOWNWARD 8388610.00+0.25 = 8388610.00 0x4b000002 ★TONEARESTと同じになっている 8388610.00+0.50 = 8388610.00 0x4b000002 8388610.00+0.75 = 8388611.00 0x4b000003 8388611.00+0.25 = 8388611.00 0x4b000003 8388611.00+0.50 = 8388612.00 0x4b000004 8388611.00+0.75 = 8388612.00 0x4b000004 -8388610.00-0.25 = -8388610.00 0xcb000002 -8388610.00-0.50 = -8388610.00 0xcb000002 -8388610.00-0.75 = -8388611.00 0xcb000003 -8388611.00-0.25 = -8388611.00 0xcb000003 -8388611.00-0.50 = -8388612.00 0xcb000004 -8388611.00-0.75 = -8388612.00 0xcb000004 TONEAREST 8388610.00+0.25 = 8388610.00 0x4b000002 8388610.00+0.50 = 8388610.00 0x4b000002 8388610.00+0.75 = 8388611.00 0x4b000003 8388611.00+0.25 = 8388611.00 0x4b000003 8388611.00+0.50 = 8388612.00 0x4b000004 8388611.00+0.75 = 8388612.00 0x4b000004 -8388610.00-0.25 = -8388610.00 0xcb000002 -8388610.00-0.50 = -8388610.00 0xcb000002 -8388610.00-0.75 = -8388611.00 0xcb000003 -8388611.00-0.25 = -8388611.00 0xcb000003 -8388611.00-0.50 = -8388612.00 0xcb000004 -8388611.00-0.75 = -8388612.00 0xcb000004 TOWARDZERO 8388610.00+0.25 = 8388610.00 0x4b000002 ★TONEARESTと同じになっている 8388610.00+0.50 = 8388610.00 0x4b000002 8388610.00+0.75 = 8388611.00 0x4b000003 8388611.00+0.25 = 8388611.00 0x4b000003 8388611.00+0.50 = 8388612.00 0x4b000004 8388611.00+0.75 = 8388612.00 0x4b000004 -8388610.00-0.25 = -8388610.00 0xcb000002 -8388610.00-0.50 = -8388610.00 0xcb000002 -8388610.00-0.75 = -8388611.00 0xcb000003 -8388611.00-0.25 = -8388611.00 0xcb000003 -8388611.00-0.50 = -8388612.00 0xcb000004 -8388611.00-0.75 = -8388612.00 0xcb000004 UPWARD 8388610.00+0.25 = 8388610.00 0x4b000002 ★TONEARESTと同じになっている 8388610.00+0.50 = 8388610.00 0x4b000002 8388610.00+0.75 = 8388611.00 0x4b000003 8388611.00+0.25 = 8388611.00 0x4b000003 8388611.00+0.50 = 8388612.00 0x4b000004 8388611.00+0.75 = 8388612.00 0x4b000004 -8388610.00-0.25 = -8388610.00 0xcb000002 -8388610.00-0.50 = -8388610.00 0xcb000002 -8388610.00-0.75 = -8388611.00 0xcb000003 -8388611.00-0.25 = -8388611.00 0xcb000003 -8388611.00-0.50 = -8388612.00 0xcb000004 -8388611.00-0.75 = -8388612.00 0xcb000004
これはGCCの最適化による影響です。O1以上の最適化を行うと、演算時の丸めモードをTONEARESTと仮定し、コンパイル時に計算できる値を事前に計算する、という最適化が行われます。
この最適化が都合が良い場合もありますが、今回のようなプログラムでは丸めモードをTONEAREST以外に変更しているので、勝手にTONEARESTを仮定してはいけません。GCCの場合-frounding-mathというオプションを付けると、デフォルトの丸めモードを仮定した最適化をやめることができます。
$ gcc -Wall -g -O2 a.c -lm -frounding-math $ ./a.out DOWNWARD ★マイナス無限大方向に丸める 8388610.00+0.25 = 8388610.00 0x4b000002 ★10.25 -> 10.00 8388610.00+0.50 = 8388610.00 0x4b000002 ★10.50 -> 10.00 8388610.00+0.75 = 8388610.00 0x4b000002 ★10.75 -> 10.00 8388611.00+0.25 = 8388611.00 0x4b000003 ★11.25 -> 11.00 8388611.00+0.50 = 8388611.00 0x4b000003 ★11.50 -> 11.00 8388611.00+0.75 = 8388611.00 0x4b000003 ★11.75 -> 11.00 -8388610.00-0.25 = -8388611.00 0xcb000003 ★-10.25 -> -11.00 -8388610.00-0.50 = -8388611.00 0xcb000003 ★-10.50 -> -11.00 -8388610.00-0.75 = -8388611.00 0xcb000003 ★-10.75 -> -11.00 -8388611.00-0.25 = -8388612.00 0xcb000004 ★-11.25 -> -12.00 -8388611.00-0.50 = -8388612.00 0xcb000004 ★-11.50 -> -12.00 -8388611.00-0.75 = -8388612.00 0xcb000004 ★-11.75 -> -12.00 TONEAREST ★最近接数に丸める、真ん中(0.5)は偶数側に丸める 8388610.00+0.25 = 8388610.00 0x4b000002 ★10.25 -> 10.00 8388610.00+0.50 = 8388610.00 0x4b000002 ★10.50 -> 10.00 8388610.00+0.75 = 8388611.00 0x4b000003 ★10.75 -> 11.00 8388611.00+0.25 = 8388611.00 0x4b000003 ★11.25 -> 11.00 8388611.00+0.50 = 8388612.00 0x4b000004 ★11.50 -> 12.00 8388611.00+0.75 = 8388612.00 0x4b000004 ★11.75 -> 12.00 -8388610.00-0.25 = -8388610.00 0xcb000002 ★-10.25 -> -10.00 -8388610.00-0.50 = -8388610.00 0xcb000002 ★-10.50 -> -10.00 -8388610.00-0.75 = -8388611.00 0xcb000003 ★-10.75 -> -11.00 -8388611.00-0.25 = -8388611.00 0xcb000003 ★-11.25 -> -11.00 -8388611.00-0.50 = -8388612.00 0xcb000004 ★-11.50 -> -12.00 -8388611.00-0.75 = -8388612.00 0xcb000004 ★-11.75 -> -12.00 TOWARDZERO ★ゼロ方向に丸める 8388610.00+0.25 = 8388610.00 0x4b000002 ★10.25 -> 10.00 8388610.00+0.50 = 8388610.00 0x4b000002 ★10.50 -> 10.00 8388610.00+0.75 = 8388610.00 0x4b000002 ★10.75 -> 10.00 8388611.00+0.25 = 8388611.00 0x4b000003 ★11.25 -> 11.00 8388611.00+0.50 = 8388611.00 0x4b000003 ★11.50 -> 11.00 8388611.00+0.75 = 8388611.00 0x4b000003 ★11.75 -> 11.00 -8388610.00-0.25 = -8388610.00 0xcb000002 ★-10.25 -> -10.00 -8388610.00-0.50 = -8388610.00 0xcb000002 ★-10.50 -> -10.00 -8388610.00-0.75 = -8388610.00 0xcb000002 ★-10.75 -> -10.00 -8388611.00-0.25 = -8388611.00 0xcb000003 ★-11.25 -> -11.00 -8388611.00-0.50 = -8388611.00 0xcb000003 ★-11.50 -> -11.00 -8388611.00-0.75 = -8388611.00 0xcb000003 ★-11.75 -> -11.00 UPWARD ★プラス無限大方向に丸める 8388610.00+0.25 = 8388611.00 0x4b000003 ★10.25 -> 11.00 8388610.00+0.50 = 8388611.00 0x4b000003 ★10.50 -> 11.00 8388610.00+0.75 = 8388611.00 0x4b000003 ★10.75 -> 11.00 8388611.00+0.25 = 8388612.00 0x4b000004 ★11.25 -> 12.00 8388611.00+0.50 = 8388612.00 0x4b000004 ★11.50 -> 12.00 8388611.00+0.75 = 8388612.00 0x4b000004 ★11.75 -> 12.00 -8388610.00-0.25 = -8388610.00 0xcb000002 ★-10.25 -> -10.00 -8388610.00-0.50 = -8388610.00 0xcb000002 ★-10.50 -> -10.00 -8388610.00-0.75 = -8388610.00 0xcb000002 ★-10.75 -> -10.00 -8388611.00-0.25 = -8388611.00 0xcb000003 ★-11.25 -> -11.00 -8388611.00-0.50 = -8388611.00 0xcb000003 ★-11.50 -> -11.00 -8388611.00-0.75 = -8388611.00 0xcb000003 ★-11.75 -> -11.00
丸めモードの切り替えが正常に働いている様子がわかります。またTONEARESTの丸めモードにした場合、ties to evenであることも観察できます。
C99のfinal draftをざっと見た限りでは、ties to evenかties away from zeroか、明確に書かれていないように見えました。規格的にはどちらなんでしょうね?実装依存なのかなあ?
在宅勤務環境を整えようと、電源タップを物色していました。電源タップを見ていると大体3つに分類できて、
前者2つはわかりやすいです。3つ目の雷ガードとは何者でしょう?前から不思議だったので、軽く調べました。
雷ガードは雷の直撃を防ぐわけではなく(それは避雷針の仕事)、落雷したとき電線に混入する1〜20kV程度のサージ電流を防ぐ仕組みのようです。パナソニックのタップは0.8kV, 1.2kV、LED照明器具は15kV、サンワサプライは15kV, 20kVを最大電圧とした製品を見つけました。
保護の仕組みですが、サンワサプライは電源系にはバリスタ(Varistor, Variable Resistor)を使っていると書いています。パナソニックのサイトも雷サージ対策にバリスタとあるので、バリスタがポピュラーな仕組みなのでしょう。
参考: 【EMC対策】雷サージ対策部品 "バリスタ" の落とし穴 - パナソニック
バリスタはいわゆる静電気(ESD, Electric Static Discharge)対策で使われる素子です。ツェナーダイオードを2つ逆接続したようなV-I特性で、電圧の正負を気にせず、とにかく高い電圧に対し低い抵抗値を持ちます。
参考: チップバリスタ - 電子デバイス・産業用機器 - パナソニック
バリスタと本体の電子機器を並列に接続します。回路に高い電圧が掛かると、バリスタ側の抵抗値が急激に下がり、バリスタ側に電流が流れます。バリスタ側に電流が逃げることで、本体の電子機器は高い電圧を食らわずに済みサージ電流から保護される仕組みです。
ESDの場合は素子が壊れないように十分な耐圧のバリスタを選定すると思いますが、雷サージの電圧は最大何Vかわかりません。時にバリスタが耐えられない電圧が掛かり、バリスタが故障します。特にバリスタがショートモードで故障し、ユーザーが気づかずに使い続けた場合、素子が加熱し火災が発生する恐れがあります。
これは実際に起きた事故で、10年前にNOATEKの雷ガード製品がバリスタの加熱&火災を起こし150万個もリコールされました。NITEの解説を見る限り、この製品は「雷ガード=バリスタ入れればOK」という甘い設計だったため、ショートモードで故障した後、加熱を検知できず火災に至ったようです。
雷を防いだら、火事になりました、なんて製品は全く笑えません。安全な設計というのは難しいですね。
こんなことばかり調べているから、全然買い物が進みません。在宅勤務環境が整うのはまだ先のようです……。
メモ: 技術系の話はFacebookから転記しておくことにした。リンク、情報追加、加筆。
手に合うワイヤレスマウスを探し続け、高級製品、小さい製品、お手ごろ製品と買いまくり、一時は家に5個くらいワイヤレスマウスがありました。
日記から遍歴を辿ってみたところ、少なくとも下記の製品を持っていたようです。
全部で11個です。こんなに買っていたとは思いませんでした……。
色々試して最終的に辿り着いたのは、Microsoftの1000円の有線マウス(Basic Optical Mouse)でした。このマウスは中身がほぼ空っぽでめちゃくちゃ軽いです。
はい、何ですか?ワイヤレスじゃないって?そうですね。ワイヤレスマウスはどうしても重くて、手首が疲れてしまいダメでした。
小さいワイヤレスマウスなら軽いですが、手の大きさと合わずやっぱり手が疲れてしまいます。どうもマウスの持ち方(親指と小指で挟むように持つ)が、小さいマウス、ワイヤレスマウスと合わないみたいです。
という訳でワイヤレスマウスは諦めました。有線マウスは安くて軽くて最高です!
メモ: 技術系の話はFacebookから転記しておくことにした。マウス遍歴を追加。
目次: Linux
昨日(2021年3月3日の日記参照)の続きです。
Windows側で使用するGDBを用意します。最初はMinGWのバイナリが使えるかと思ったのですが、MinGWのバイナリはWindowsの実行形式(COFF)のデバッグ用で、Linuxの実行形式(ELF)は認識せず、ダメでした。残念。
GDBはbinutilsに含まれています。ビルド方法を下記に示します。バージョンは何でも良いと思いますが、私はMinGWも使っている2.32にしました。
$ git clone git://sourceware.org/git/binutils-gdb.git $ cd binutils-gdb $ git checkout -b 2_32 binutils-2_32 $ mkdir build $ cd build $ ../configure \ --host=x86_64-w64-mingw32 \ --target=x86_64-unknown-linux-gnu \ --prefix=`pwd`/_install \ --disable-werror \ --with-expat=no \ --enable-sim \ --enable-libdecnumber \ --enable-libreadline \ --enable-gas \ --enable-binutils \ --enable-ld \ --disable-gold \ --enable-gprof $ make -j8 $ make install
ビルドが成功すると、build/_installディレクトリにWindows用のbinutilsのバイナリ(GDBも含む)がインストールされます。フォルダごとWindows側にコピーすれば動きます。ディレクトリの名前 _installはWindows側に持っていったあとに変更しても構いません。
比較的、新しい環境(Ubuntu 20など)だとMinGWが何か変らしく、gdb/common/common-defs.hにある _FORTIFY_SOURCEの定義を消さないと、リンクの時に __memcpy_chk() などの関数が見当たらないと言われてエラーになります。
CXXLD gdb.exe /usr/bin/x86_64-w64-mingw32-ld: ada-tasks.o:/usr/share/mingw-w64/include/string.h:202: undefined reference to `__memcpy_chk' /usr/bin/x86_64-w64-mingw32-ld: amd64-tdep.o:/usr/share/mingw-w64/include/string.h:202: undefined reference to `__memcpy_chk' /usr/bin/x86_64-w64-mingw32-ld: breakpoint.o:/usr/share/mingw-w64/include/string.h:228: undefined reference to `__strcpy_chk' /usr/bin/x86_64-w64-mingw32-ld: breakpoint.o:/usr/share/mingw-w64/include/string.h:228: undefined reference to `__strcpy_chk' ...
とりあえず下記のパッチでエラーは回避できますが、おそらく正しい直し方ではありません。原因は調べていません。もしご存知の方は教えていただけると嬉しいです。
diff --git a/gdb/common/common-defs.h b/gdb/common/common-defs.h
index e574de5ed66..a9b0520b4c5 100644
--- a/gdb/common/common-defs.h
+++ b/gdb/common/common-defs.h
@@ -69,7 +69,7 @@
then we don't do anything. */
#if !defined _FORTIFY_SOURCE && defined __OPTIMIZE__ && __OPTIMIZE__ > 0
-#define _FORTIFY_SOURCE 2
+//#define _FORTIFY_SOURCE 2
#endif
#include <stdarg.h>
面倒であれば、ビルド済みのバイナリ( binutils_x86_64-unknown-linux-gnu.zip)をどこか適当なディレクトリに展開してもらえれば動くはずです。Windows 10で動作確認しています。
Visual Studio Codeをインストールします。Microsoftのサイトからダウンロードできます(Visual Studio Code - Code Editing. Redefined)。
はじめにC/C++ Extensionをインストールします。GDBと連携するためです。
VSCode C/C++ Extensionのインストール画面
先ほどのテストプログラムが置いてあるディレクトリを開きます。メニューの [File] - [Open Folder] です。ここではZ:\projects\c\test_argvにあるとします。
左側のタブからRun and Debugを選択します。Ctrl + Shift + Dでも良いです。次に青いRun and Debugボタンを押します。するとSelect Environmentというコンボボックスが出てくるのでC++ (GDB/LLDB) を選択します。
ソースコードペインにlaunch.jsonのテンプレートが表示されると思うので、下記の内容に書き換えます。miDebuggerPathやmiDebuggerServerAddressは適宜、環境に合わせて書き換えてください。
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Attach",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/a.out",
"cwd": "${workspaceFolder}",
"MIMode": "gdb",
"miDebuggerPath": "c:\\app\\binutils\\bin\\x86_64-unknown-linux-gnu-gdb.exe",
"miDebuggerServerAddress": "192.168.1.2:1234",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true,
},
{
"description": "Relace absolute path of source code",
"ignoreFailures": false,
"text": "set substitute-path /home/katsuhiro/share/falcon z:/",
},
],
},
]
}
ここで大事なのはsetupCommandsのset substitute-pathです。gdbはLinux側の絶対パスでソースコードの場所を通知してくることがあります。Linux側の絶対パスを渡されてもWindows側ではファイルを探せませんから、絶対パスの一部をWindows側に存在するパスに置き換えることで、VSCodeがソースコードを見つけられるように設定してあげないと、こんなエラーで怒られます。
今回の例では、
このような設定にしていますから、Windows側のネットワークドライブZ:/ が、Linux側の /home/katsuhiro/share/falconを指していることを伝えるために、上記の例のように "set substitute-path /home/katsuhiro/share/falcon z:/" にします。もし対応がわからない場合はSambaの設定を確認しましょう。
いよいよ最終目的であるLinux側のgdbserverと、Windows側のVSCode + GDBを連携させます。図示するとこのような構成です。
WindowsからLinuxアプリのデバッグ、それぞれの役割(再掲)
実際にやってみましょう。最初にLinux側でgdbserverを起動します。
$ gdbserver --multi localhost:1234 ./a.out a b c d Process ./a.out created; pid = 15409 Listening on port 1234 Remote debugging from host ::ffff:192.168.1.112, port 64957 ★mainでbreakしたあとcontinueすると下記が出力される argc: 5 argv[1]: aa argv[2]: bb argv[3]: cc argv[4]: dd Child exited with status 0
その後、VSCodeでmainにブレークポイントを設定します。F5キーを押してデバッグ開始すると、main() 関数で停止し、ソースコードが表示されるはずです。
うまくいきました、良かった良かった。
Unable to start debugging. Unexpected GDB output from command... のように言われて、デバッグできない場合は、
上記を今一度チェックしてみてください。特にgdbserverはデバッグの度に毎回起動しなければなりません。割と忘れがちです。これ非常に面倒なので、改善方法が既にありそうな気がします(今はわかりません)。
そこに気づかれましたか、鋭いですね、おっしゃる通りです。私もbinutilsをビルドし終わった辺りで、もしやVSCodeをLinux側で動かせば何の苦労もなかったのでは?と気づきましたが、遅すぎたのでここまで突っ走りました。正直、Windowsを使うだけ面倒で、何も利点がないです。なるほど、この方法を実践している人が誰もいないわけです。
Windowsからデバッグだけする方法が向いている人、環境ですか……?うーん、ほとんど居ないと思いますが、強いていえばLinux PCのGUI環境を整備しておらず、整備も面倒と思っていて、普段遣いのWindows PCしか持っていないような方でしょうか。この手順も大概面倒くさいけどね。
目次: Linux
同じことをしている人があまり居なさそうだったので、メモしておきます。
きっかけはGCCのコードをGDBのCUIモードで追っていて辛くなったことです。GCCのコードは超ぐちゃぐちゃの悲惨なコードで非常に追いづらく、GDBをもってしても何が起きているのか把握するのは困難です。せめてデバッガの画面くらいはGUIにして、見やすくできないか、と考えました。
WindowsからLinuxアプリのデバッグ、それぞれの役割
想定する構成は上記のとおりで、Linux側にはGUIがなく(ディスプレイを繋いでいない、など)、Windows側はデバッグのみで、Linux側でその他の全て(ビルドなど)を行う想定です。
この記事を読んでいるということは、既に何かデバッグしたいアプリケーションがあると思いますから、基本的なツールであるgccやmakeなどのインストールは省略させてもらいます。gdbserverをインストールするだけで良いはずです。
# apt-get install gdbserver Reading package lists... Done Building dependency tree Reading state information... Done The following NEW packages will be installed: gdbserver 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 0 B/458 kB of archives. After this operation, 1165 kB of additional disk space will be used. Selecting previously unselected package gdbserver. (Reading database ... 99167 files and directories currently installed.) Preparing to unpack .../gdbserver_8.2.1-2+b3_amd64.deb ... Unpacking gdbserver (8.2.1-2+b3) ... Setting up gdbserver (8.2.1-2+b3) ...
デバッグするプログラムは何でも良いですが、下記を使用します。渡した引数を表示するだけのプログラムです。
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("argc: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
$ gcc -Wall -g -O0 a.c $ ./a.out a b c argc: 4 argv[1]: a argv[2]: b argv[3]: c
まずは普通にCUIからGDBで追ってみます。図示するとこのような構成です。
あえてやる必要もなさそうですけど、このあとの一貫性のために試します。
$ gdb a.out ... For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from a.out... (gdb) b main Breakpoint 1 at 0x1144: file a.c, line 5. (gdb) r a b c d Starting program: /home/katsuhiro/share/falcon/projects/c/test_argv/a.out a b c d Breakpoint 1, main (argc=5, argv=0x7fffffffdca8) at a.c:5 5 printf("argc: %d\n", argc); (gdb) c Continuing. argc: 5 argv[1]: a argv[2]: b argv[3]: c argv[4]: d [Inferior 1 (process 4008866) exited normally]
普通です。次はgdbserverと連携させリモートでGDBで追ってみます。図示するとこのような構成です。
実際にやってみましょう。ターミナルを2つ用意してください。
★★ターミナル1つ目 $ gdbserver --multi localhost:1234 ./a.out a b c d Process ./a.out created; pid = 4008908 Listening on port 1234 Remote debugging from host ::1, port 32918 ★mainでbreakしたあとのcontinueを実行すると下記が出力される argc: 5 argv[1]: a argv[2]: b argv[3]: c argv[4]: d Child exited with status 0 ★★ターミナル2つ目 $ gdb ... For help, type "help". Type "apropos word" to search for commands related to "word". (gdb) target remote :1234 Remote debugging using :1234 Reading test_argv/a.out from remote target... warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead. Reading test_argv/a.out from remote target... Reading symbols from target:test_argv/a.out... Reading /lib64/ld-linux-x86-64.so.2 from remote target... Reading /lib64/ld-linux-x86-64.so.2 from remote target... Reading symbols from target:/lib64/ld-linux-x86-64.so.2... Reading symbols from /usr/lib/debug/.build-id/5b/e47e85c990f390b0dccb6ca9dc3e70f410dc6a.debug... 0x00007ffff7fd3090 in _start () from target:/lib64/ld-linux-x86-64.so.2 (gdb) b main Breakpoint 1 at 0x555555555144: file a.c, line 5. (gdb) c Continuing. Reading /lib/x86_64-linux-gnu/libc.so.6 from remote target... Breakpoint 1, main (argc=5, argv=0x7fffffffdd18) at a.c:5 5 printf("argc: %d\n", argc); (gdb) l 1 #include <stdio.h> 2 3 int main(int argc, char *argv[]) 4 { 5 printf("argc: %d\n", argc); 6 7 for (int i = 1; i < argc; i++) { 8 printf("argv[%d]: %s\n", i, argv[i]); 9 } 10 (gdb) c Continuing. [Inferior 1 (process 4008908) exited normally]
ブレークもできますし、ソースコードも表示されます。良い感じですね。
使い勝手はGDB単体でデバッグするときとほぼ同じですが、1点だけ注意があります。デバッガとして動作するのはgdbserverであり、プログラムのprintfなどの標準出力は、gdbserverを動かしているターミナル側に出ることに注意してください。この使い方におけるGDBはgdbserverと通信するだけの役です。
Sambaのセットアップについては、ここで解説せずとも、詳しいサイトがたくさんあると思いますので、そちらをご参照ください。
思ったより長くなってきたので、続きは次回に。
今まで、いわゆる日本語配列のキー配列のことを漠然とJIS配列と呼んでいましたが、どうもこれは正確ではないようです。
技術者たるもの、規格を粗末に扱ってはいけませんよね??
JIS配列を定めているのはJIS X 6002-1980「情報処理系けん盤配列」です。この配列を見ると一目瞭然ですが、アルファベット、スペースなどは一致するものの、他のキーは全く違う位置にあったり、そもそもなかったりします。規格書は有料(1,100円)ですが、PFUのサイトでJIS配列の図をタダで拝めます。
PFUのサイトへのリンク:
https://www.pfu.fujitsu.com/hhkeyboard/pfutechreview/section3.html
現状、主流の日本語キー配列はOADG(The PC Open Architecture Developers Group)が規格化した配列です。OADGは2004年に解散していますが、規格書はInternet Archiveで見ることができます。
Internet Archive: OADGテクニカル・リファレンス ハードウェア編1991年へのリンク:
https://archive.org/details/OADG1991/
規格の付録Aを見ると、
の3つが書かれています。市販のキーボードは、各社ともに適当に魔改造しているため、OADG規格と全く同じキー配列のキーボードは存在しないと思います。たぶん。
実例を出すと、Logicool K295はかなり109Aに近いです。しかし、右Windowsキーが欠けています(このタイプ、俗に108キーなどと呼ばれていますね)。
FILCO Majestouchも素直な109Aに近いですが、右Appキーがなく、右WindowsキーがFnキーに置換されています。
FILCO Majestouch Convertible 2のキー配列
私の場合は、最下段は 左Ctrl, 左Win, 左Alt, スペース, 右Alt, 右Ctrlしか使わないので、K295やMajestouchのように使わないキーが欠けていても気になりませんが、使う人からすると、何てことするの!?という気分になるでしょう。
さらに言うと、無変換、変換、ひらがなキーも使わないので、使っているキーの種類だけ考えたら、いわゆる英語配列(OADG 104型)で十分に思えます。しかし日本語配列で練習したため、英語配列は記号類を間違えまくります。私物で購入したことはありません。打てて損はないので、もうちょい修行しても良いかもしれませんね……?
メモ: 技術系の話はFacebookから転記しておくことにした。写真追加、加筆。
< | 2021 | > | ||||
<< | < | 03 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | - | - | - |
合計:
本日: