Saya punya paket R dengan kode kompilasi C yang sudah relatif stabil untuk sementara waktu dan sering diuji terhadap berbagai platform dan kompiler (windows / osx / debian / fedora gcc / clang).
Baru-baru ini platform baru ditambahkan untuk menguji paket lagi:
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"
Pada titik mana kode yang dikompilasi segera mulai melakukan segmentasi di sepanjang baris berikut:
*** caught segfault ***
address 0x1d00000001, cause 'memory not mapped'
Saya dapat mereproduksi segfault secara konsisten dengan menggunakan rocker/r-base
wadah buruh pelabuhan gcc-10.0.1
dengan tingkat pengoptimalan -O2
. Menjalankan optimasi yang lebih rendah menghilangkan masalah. Menjalankan set-up lainnya, termasuk di bawah valgrind (keduanya -O0 dan -O2), UBSAN (gcc / clang), tidak menunjukkan masalah sama sekali. Saya juga cukup yakin ini berjalan di bawah gcc-10.0.0
, tetapi tidak memiliki data.
Saya menjalankan gcc-10.0.1 -O2
versi itu gdb
dan memperhatikan sesuatu yang terasa aneh bagi saya:
Sementara melangkah melalui bagian yang disorot tampak inisialisasi elemen kedua array dilewati ( R_alloc
adalah pembungkus di sekitar malloc
pengumpulan sampah sendiri ketika mengembalikan kontrol ke R; segfault terjadi sebelum kembali ke R). Kemudian, program macet ketika elemen yang tidak diinisialisasi (dalam versi gcc.10.0.1 -O2) diakses.
Saya memperbaikinya dengan menginisialisasi elemen secara eksplisit di mana-mana dalam kode yang akhirnya mengarah ke penggunaan elemen, tetapi itu seharusnya benar-benar diinisialisasi ke string kosong, atau setidaknya itulah yang akan saya asumsikan.
Apakah saya kehilangan sesuatu yang jelas atau melakukan sesuatu yang bodoh? Keduanya cukup masuk akal karena C adalah bahasa kedua saya sejauh ini . Hanya aneh bahwa ini baru saja dipotong sekarang, dan saya tidak tahu apa yang coba dilakukan oleh kompiler.
UPDATE : Petunjuk untuk mereproduksi ini, meskipun ini hanya akan mereproduksi selama debian:testing
wadah buruh pelabuhan memiliki gcc-10
di gcc-10.0.1
. Juga, jangan hanya menjalankan perintah ini jika Anda tidak percaya padaku .
Maaf ini bukan contoh minimal yang dapat direproduksi.
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
Kemudian di konsol R, setelah mengetik run
untuk mendapatkan gdb
untuk menjalankan program:
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)
Memeriksa di gdb menunjukkan cukup cepat (jika saya mengerti benar) yang
CSR_strmlen_x
mencoba mengakses string yang tidak diinisialisasi.
UPDATE 2 : ini adalah fungsi yang sangat rekursif, dan di atas itu bit inisialisasi string dipanggil berkali-kali. Ini sebagian besar b / c saya sedang malas, kita hanya perlu string diinisialisasi untuk satu kali kita benar-benar menemukan sesuatu yang ingin kita laporkan dalam rekursi, tetapi lebih mudah untuk menginisialisasi setiap kali mungkin untuk menemukan sesuatu. Saya menyebutkan ini karena apa yang akan Anda lihat selanjutnya menunjukkan beberapa inisialisasi, tetapi hanya satu dari mereka (mungkin yang dengan alamat <0x1400000001>) sedang digunakan.
Saya tidak dapat menjamin bahwa hal-hal yang saya perlihatkan di sini secara langsung berkaitan dengan elemen yang menyebabkan segfault (meskipun itu adalah akses alamat ilegal yang sama), tetapi ketika @ nate-eldredge bertanya, itu menunjukkan bahwa elemen array tidak diinisialisasi baik sebelum kembali atau tepat setelah kembali dalam fungsi panggilan. Perhatikan fungsi panggilan menginisialisasi 8 dari ini, dan saya tunjukkan semuanya, dengan semuanya dipenuhi dengan sampah atau memori yang tidak dapat diakses.
UPDATE 3 , pembongkaran fungsi yang dimaksud:
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.
PEMBARUAN 4 :
Jadi, mencoba mengurai melalui standar di sini adalah bagian-bagiannya yang tampaknya relevan ( draft C11 ):
6.3.2.3 Konversi Par7> Operan Lain> Pointer
Pointer ke tipe objek dapat dikonversi ke pointer ke tipe objek yang berbeda. Jika pointer yang dihasilkan tidak sejajar dengan benar ( 68) untuk tipe yang direferensikan, perilaku tidak terdefinisi.
Kalau tidak, ketika dikonversi kembali lagi, hasilnya harus membandingkan sama dengan pointer asli. Ketika pointer ke objek dikonversi ke pointer ke tipe karakter, hasilnya menunjuk ke byte terendah dari objek. Peningkatan hasil berturut-turut, hingga ukuran objek, menghasilkan pointer ke byte tersisa dari objek.
6.5 Par6 Ekspresi
Jenis objek yang efektif untuk akses ke nilai yang disimpannya adalah tipe objek yang dinyatakan, jika ada. 87) Jika suatu nilai disimpan ke dalam objek yang tidak memiliki tipe yang dideklarasikan melalui nilai lv yang memiliki tipe yang bukan tipe karakter, maka tipe nilai tersebut menjadi tipe efektif objek untuk akses itu dan untuk akses selanjutnya yang tidak. ubah nilai yang disimpan. Jika nilai disalin ke objek yang tidak memiliki tipe yang dideklarasikan menggunakan memcpy atau memmove, atau disalin sebagai array tipe karakter, maka tipe efektif objek yang dimodifikasi untuk akses tersebut dan untuk akses selanjutnya yang tidak mengubah nilai adalah jenis objek yang efektif dari mana nilai disalin, jika ada. Untuk semua akses lainnya ke objek yang tidak memiliki tipe yang dideklarasikan, tipe efektif objek hanyalah tipe nilai yang digunakan untuk akses.
87) Objek yang dialokasikan tidak memiliki tipe yang dideklarasikan.
IIUC R_alloc
mengembalikan offset ke malloc
blok ed yang dijamin akan double
selaras, dan ukuran blok setelah offset adalah dari ukuran yang diminta (ada juga alokasi sebelum offset untuk data spesifik R). R_alloc
melemparkan penunjuk itu ke belakang (char *)
.
Bagian 6.2.5 Par 29
Penunjuk untuk membatalkan harus memiliki persyaratan representasi dan perataan yang sama seperti penunjuk ke tipe karakter. 48) Demikian pula, pointer ke versi yang memenuhi syarat atau tidak memenuhi syarat dari jenis yang kompatibel harus memiliki persyaratan representasi dan perataan yang sama. Semua pointer ke tipe struktur harus memiliki persyaratan representasi dan perataan yang sama satu sama lain.
Semua pointer ke tipe-tipe serikat pekerja harus memiliki persyaratan representasi dan penyelarasan yang sama satu sama lain.
Pointer ke tipe lain tidak perlu memiliki representasi atau persyaratan penyelarasan yang sama.48) Persyaratan representasi dan penyelarasan yang sama dimaksudkan untuk menyiratkan argumen yang dapat dipertukarkan ke fungsi, mengembalikan nilai dari fungsi, dan anggota serikat pekerja.
Jadi pertanyaannya adalah "apakah kita diizinkan untuk menyusun kembali (char *)
ke (const char **)
dan menulis sebagai (const char **)
". Bacaan saya di atas adalah bahwa selama pointer pada sistem kode dijalankan memiliki keselarasan yang kompatibel dengan double
penyelarasan, maka tidak apa-apa.
Apakah kita melanggar "aliasing ketat"? yaitu:
6.5 Par 7
Objek harus memiliki nilai tersimpan diakses hanya oleh ekspresi lvalue yang memiliki salah satu dari jenis berikut: 88)
- jenis yang kompatibel dengan jenis objek yang efektif ...
88) Maksud dari daftar ini adalah untuk menentukan keadaan-keadaan di mana suatu objek mungkin atau mungkin tidak disebutkan.
Jadi, apa yang seharusnya dikompilasi oleh kompilator jenis objek yang ditunjuk oleh res.target
(atau res.current
)? Agaknya tipe yang dideklarasikan (const char **)
, atau apakah ini sebenarnya ambigu? Rasanya bagi saya bahwa tidak dalam kasus ini hanya karena tidak ada 'nilai' lain dalam ruang lingkup yang mengakses objek yang sama.
Saya akui saya berjuang keras untuk mengeluarkan akal dari bagian-bagian standar ini.
-mtune=native
mengoptimalkan untuk CPU tertentu yang dimiliki mesin Anda. Itu akan berbeda untuk penguji yang berbeda dan mungkin menjadi bagian dari masalah. Jika Anda menjalankan kompilasi dengan -v
Anda harus dapat melihat keluarga cpu mana yang ada di mesin Anda (misalnya -mtune=skylake
di komputer saya).
disassemble
instruksi di dalam gdb.