私はかなり長い間比較的安定していて、さまざまなプラットフォームとコンパイラ(windows / osx / debian / fedora gcc / clang)に対して頻繁にテストされているCコンパイル済みコードを含むRパッケージを持っています。
最近、パッケージを再度テストするために新しいプラットフォームが追加されました:
Logs from checks with gcc trunk aka 10.0.1 compiled from source
on Fedora 30. (For some archived packages, 10.0.0.)
x86_64 Fedora 30 Linux
FFLAGS="-g -O2 -mtune=native -Wall -fallow-argument-mismatch"
CFLAGS="-g -O2 -Wall -pedantic -mtune=native -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong -fstack-clash-protection -fcf-protection"
CXXFLAGS="-g -O2 -Wall -pedantic -mtune=native -Wno-ignored-attributes -Wno-deprecated-declarations -Wno-parentheses -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong -fstack-clash-protection -fcf-protection"
その時点で、コンパイルされたコードはすぐに次の行に沿ってsegfaultingを開始しました。
*** caught segfault ***
address 0x1d00000001, cause 'memory not mapped'
最適化レベルでDocker rocker/r-base
コンテナーを使用することにより、segfaultを一貫して再現できました。低い最適化を実行すると、問題が解消されます。valgrind(-O0と-O2の両方)、UBSAN(gcc / clang)を含む他のセットアップを実行しても、まったく問題はありません。また、これはで実行されたと合理的に確信していますが、データがありません。gcc-10.0.1
-O2
gcc-10.0.0
私はgcc-10.0.1 -O2
バージョンを実行しましたが、gdb
私には奇妙に見えることに気づきました:
強調表示されたセクションをステップ実行している間、配列の2番目の要素の初期化がスキップされているように見えます(Rに制御を返すときに自己ガベージコレクションのR_alloc
ラッパーmalloc
です。Rに戻る前にsegfaultが発生します)。その後、初期化されていない要素(gcc.10.0.1 -O2バージョン)にアクセスすると、プログラムがクラッシュします。
問題の要素を最終的に要素の使用につながるコードのすべての場所で明示的に初期化することによってこれを修正しましたが、実際には空の文字列に初期化されている必要があり、少なくとも私が想定したとおりです。
私は明白な何かを逃したり、愚かなことをしていますか?Cはで私の第二言語であるとして、どちらが合理的に可能性がありますはるか。これが今ちょうど現れたのは奇妙で、コンパイラが何をしようとしているのか理解できません。
更新:これを再現する手順。ただし、debian:testing
Dockerコンテナがにある場合に限り再現さgcc-10
れgcc-10.0.1
ます。また、私を信頼していない場合は、これらのコマンドを実行しないでください。
申し訳ありませんが、これは最小限の再現可能な例ではありません。
docker pull rocker/r-base
docker run --rm -ti --security-opt seccomp=unconfined \
rocker/r-base /bin/bash
apt-get update
apt-get install gcc-10 gdb
gcc-10 --version # confirm 10.0.1
# gcc-10 (Debian 10-20200222-1) 10.0.1 20200222 (experimental)
# [master revision 01af7e0a0c2:487fe13f218:e99b18cf7101f205bfdd9f0f29ed51caaec52779]
mkdir ~/.R
touch ~/.R/Makevars
echo "CC = gcc-10
CFLAGS = -g -O2 -Wall -pedantic -mtune=native -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong -fstack-clash-protection -fcf-protection
" >> ~/.R/Makevars
R -d gdb --vanilla
次に、Rコンソールで、プログラムを実行run
するためgdb
に入力した後:
f.dl <- tempfile()
f.uz <- tempfile()
github.url <- 'https://github.com/brodieG/vetr/archive/v0.2.8.zip'
download.file(github.url, f.dl)
unzip(f.dl, exdir=f.uz)
install.packages(
file.path(f.uz, 'vetr-0.2.8'), repos=NULL,
INSTALL_opts="--install-tests", type='source'
)
# minimal set of commands to segfault
library(vetr)
alike(pairlist(a=1, b="character"), pairlist(a=1, b=letters))
alike(pairlist(1, "character"), pairlist(1, letters))
alike(NULL, 1:3) # not a wild card at top level
alike(list(NULL), list(1:3)) # but yes when nested
alike(list(NULL, NULL), list(list(list(1, 2, 3)), 1:25))
alike(list(NULL), list(1, 2))
alike(list(), list(1, 2))
alike(matrix(integer(), ncol=7), matrix(1:21, nrow=3))
alike(matrix(character(), nrow=3), matrix(1:21, nrow=3))
alike(
matrix(integer(), ncol=3, dimnames=list(NULL, c("R", "G", "B"))),
matrix(1:21, ncol=3, dimnames=list(NULL, c("R", "G", "B")))
)
# Adding tests from docs
mx.tpl <- matrix(
integer(), ncol=3, dimnames=list(row.id=NULL, c("R", "G", "B"))
)
mx.cur <- matrix(
sample(0:255, 12), ncol=3, dimnames=list(row.id=1:4, rgb=c("R", "G", "B"))
)
mx.cur2 <-
matrix(sample(0:255, 12), ncol=3, dimnames=list(1:4, c("R", "G", "B")))
alike(mx.tpl, mx.cur2)
CSR_strmlen_x
初期化されていない文字列にアクセスしようとしていることを(私が正しく理解していれば)gdbで調べると、かなりすばやく表示さ
れます。
UPDATE 2:これは非常に再帰的な関数であり、その上、文字列初期化ビットが何度も呼び出されます。これはほとんどb / cです。怠惰でした。再帰で報告したいものに実際に遭遇したときに一度だけ初期化された文字列が必要ですが、何かに遭遇する可能性があるたびに初期化する方が簡単でした。次に説明するのは複数の初期化を示していますが、そのうちの1つ(おそらくアドレス<0x1400000001>を持つもの)のみが使用されているためです。
私がここに表示しているものがsegfaultを引き起こした要素に直接関連していることは保証できません(それは同じ不正なアドレスアクセスですが)、@ nate-eldredgeが尋ねたように、配列要素が呼び出し関数での復帰直前または復帰直後のいずれかに初期化されます。呼び出し側の関数がこれらのうち8つを初期化していることに注意してください。すべてがゴミまたはアクセスできないメモリで満たされているので、すべて表示しています。
UPDATE 3、問題の機能の分解:
Breakpoint 1, ALIKEC_res_strings_init () at alike.c:75
75 return res;
(gdb) p res.current[0]
$1 = 0x7ffff46a0aa5 "%s%s%s%s"
(gdb) p res.current[1]
$2 = 0x1400000001 <error: Cannot access memory at address 0x1400000001>
(gdb) disas /m ALIKEC_res_strings_init
Dump of assembler code for function ALIKEC_res_strings_init:
53 struct ALIKEC_res_strings ALIKEC_res_strings_init() {
0x00007ffff4687fc0 <+0>: endbr64
54 struct ALIKEC_res_strings res;
55
56 res.target = (const char **) R_alloc(5, sizeof(const char *));
0x00007ffff4687fc4 <+4>: push %r12
0x00007ffff4687fc6 <+6>: mov $0x8,%esi
0x00007ffff4687fcb <+11>: mov %rdi,%r12
0x00007ffff4687fce <+14>: push %rbx
0x00007ffff4687fcf <+15>: mov $0x5,%edi
0x00007ffff4687fd4 <+20>: sub $0x8,%rsp
0x00007ffff4687fd8 <+24>: callq 0x7ffff4687180 <R_alloc@plt>
0x00007ffff4687fdd <+29>: mov $0x8,%esi
0x00007ffff4687fe2 <+34>: mov $0x5,%edi
0x00007ffff4687fe7 <+39>: mov %rax,%rbx
57 res.current = (const char **) R_alloc(5, sizeof(const char *));
0x00007ffff4687fea <+42>: callq 0x7ffff4687180 <R_alloc@plt>
58
59 res.target[0] = "%s%s%s%s";
0x00007ffff4687fef <+47>: lea 0x1764a(%rip),%rdx # 0x7ffff469f640
0x00007ffff4687ff6 <+54>: lea 0x18aa8(%rip),%rcx # 0x7ffff46a0aa5
0x00007ffff4687ffd <+61>: mov %rcx,(%rbx)
60 res.target[1] = "";
61 res.target[2] = "";
0x00007ffff4688000 <+64>: mov %rdx,0x10(%rbx)
62 res.target[3] = "";
0x00007ffff4688004 <+68>: mov %rdx,0x18(%rbx)
63 res.target[4] = "";
0x00007ffff4688008 <+72>: mov %rdx,0x20(%rbx)
64
65 res.tar_pre = "be";
66
67 res.current[0] = "%s%s%s%s";
0x00007ffff468800c <+76>: mov %rax,0x8(%r12)
0x00007ffff4688011 <+81>: mov %rcx,(%rax)
68 res.current[1] = "";
69 res.current[2] = "";
0x00007ffff4688014 <+84>: mov %rdx,0x10(%rax)
70 res.current[3] = "";
0x00007ffff4688018 <+88>: mov %rdx,0x18(%rax)
71 res.current[4] = "";
0x00007ffff468801c <+92>: mov %rdx,0x20(%rax)
72
73 res.cur_pre = "is";
74
75 return res;
=> 0x00007ffff4688020 <+96>: lea 0x14fe0(%rip),%rax # 0x7ffff469d007
0x00007ffff4688027 <+103>: mov %rax,0x10(%r12)
0x00007ffff468802c <+108>: lea 0x14fcd(%rip),%rax # 0x7ffff469d000
0x00007ffff4688033 <+115>: mov %rbx,(%r12)
0x00007ffff4688037 <+119>: mov %rax,0x18(%r12)
0x00007ffff468803c <+124>: add $0x8,%rsp
0x00007ffff4688040 <+128>: pop %rbx
0x00007ffff4688041 <+129>: mov %r12,%rax
0x00007ffff4688044 <+132>: pop %r12
0x00007ffff4688046 <+134>: retq
0x00007ffff4688047: nopw 0x0(%rax,%rax,1)
End of assembler dump.
更新4:
したがって、ここで標準を解析しようとすると、関連があるように見える部分があります(C11ドラフト):
6.3.2.3 Par7変換>その他のオペランド>ポインタ
オブジェクト型へのポインタは、別のオブジェクト型へのポインタに変換される場合があります。 結果のポインターが参照された型に対して正しく配置されていない場合 68)、動作は未定義です。
それ以外の場合、再度変換すると、結果は元のポインタと同じになります。オブジェクトへのポインターが文字型へのポインターに変換されると、結果はオブジェクトのアドレス指定された最下位バイトを指します。オブジェクトのサイズまで、結果を連続してインクリメントすると、オブジェクトの残りのバイトへのポインターが生成されます。
6.5 Par6式
格納された値にアクセスするためのオブジェクトの有効なタイプは、オブジェクトの宣言されたタイプです(存在する場合)。87)文字型ではない型を持つ左辺値を通じて宣言された型を持たないオブジェクトに値が格納されている場合、左辺値の型は、そのアクセスおよびそれ以降のアクセスではないオブジェクトの有効な型になります保存された値を変更します。値がmemcpyまたはmemmoveを使用して宣言されたタイプを持たないオブジェクトにコピーされるか、文字タイプの配列としてコピーされる場合、そのアクセスおよび値を変更しない後続のアクセスの変更されたオブジェクトの有効なタイプは、値のコピー元のオブジェクトの有効なタイプ(存在する場合)。 宣言された型を持たないオブジェクトへの他のすべてのアクセスの場合、オブジェクトの有効な型は、アクセスに使用される左辺値の型になります。
87)割り当てられたオブジェクトには宣言された型がありません。
IIUC R_alloc
は、整列malloc
が保証されているedブロックへdouble
のオフセットと、オフセット後のブロックのサイズが要求されたサイズになります(R固有のデータのオフセットの前にも割り当てがあります)。 戻り時にR_alloc
そのポインタをキャストします(char *)
。
セクション6.2.5パー29
voidへのポインタは、文字型へのポインタと同じ表現および配置要件を持たなければならない。48)同様に、互換性のある型の修飾されたバージョンまたは修飾されていないバージョンへのポインタは、同じ表現と配置の要件を持たなければならない。構造体型へのすべてのポインタは、互いに同じ表現と配置の要件を持たなければなりません。
共用体型へのすべてのポインタは、互いに同じ表現と配置の要件を持たなければなりません。
他の型へのポインターは、同じ表現または配置要件を持つ必要はありません。48)同じ表現と配置の要件は、関数、関数からの戻り値、および共用体のメンバーへの互換性のある引数を意味します。
したがって、問題は「toを再キャストし(char *)
て(const char **)
それに書き込むことを許可されているか(const char **)
」です。上記の私の読みは、コードが実行されるシステム上のポインターが配置と互換性のあるdouble
配置である限り、それで問題ありません。
「厳密なエイリアス」に違反していますか?つまり:
6.5パー7
オブジェクトは、次のいずれかのタイプの左辺値式によってのみアクセスされる格納された値を持つものとします。88)
—オブジェクトの有効なタイプと互換性のあるタイプ...
88)このリストの目的は、オブジェクトがエイリアスされる場合とされない場合がある状況を指定することです。
では、コンパイラは(または)が指すオブジェクトの有効なタイプをどのように考えるべきでしょうか?おそらく宣言された型、またはこれは実際にあいまいですか?同じオブジェクトにアクセスするスコープ内に他の「左辺値」がないため、この場合だけではないように感じます。res.target
res.current
(const char **)
私は、標準のこれらのセクションから意味を引き出すために力を尽くして努力していることを認めます。
-mtune=native
マシンに搭載されている特定のCPU向けに最適化します。これはテスターによって異なり、問題の一部である可能性があります。コンパイルを実行すると-v
、マシン(たとえば-mtune=skylake
、私のコンピューター)にあるCPUファミリーを確認できます。
disassemble
gdb内の命令を使用することもできます。