Ben oldukça uzun bir süre nispeten kararlı ve çok çeşitli platformlar ve derleyiciler (windows / osx / debian / fedora gcc / clang) karşı test edilir C derlenmiş kod ile bir R paketi var .
Son zamanlarda paketi tekrar test etmek için yeni bir platform eklendi:
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"
Bu noktada derlenen kod derhal bu satırlar boyunca segfajlamaya başladı:
*** caught segfault ***
address 0x1d00000001, cause 'memory not mapped'
Optimizasyon seviyesi ile rocker/r-base
docker konteyner kullanarak sürekli segfault çoğaltmak mümkün . Daha düşük bir optimizasyon çalıştırmak sorundan kurtulur. Valgrind (hem -O0 hem de -O2), UBSAN (gcc / clang) dahil olmak üzere başka bir kurulum çalıştırmak hiçbir sorun göstermez. Ayrıca bunun altında kaldığından da eminim , ancak veri yok.gcc-10.0.1
-O2
gcc-10.0.0
gcc-10.0.1 -O2
Sürümü çalıştırdım ve gdb
bana garip görünen bir şey fark ettim:
Vurgulanan bölümü atlama iken dizilerin ikinci elemanların başlatma atlanır belirir ( R_alloc
etrafında sarıcı malloc
; segfault R geri dönmeden önce olur R kontrol geri benlik çöp toplar). Daha sonra, başlatılmamış öğeye (gcc.10.0.1 -O2 sürümünde) erişildiğinde program çöker.
Bu sorunu açıkça öğenin kullanımına yol açan kodun her yerinde başlatarak bu düzeltildi, ama gerçekten boş bir dize için başlatılmış olması gerekirdi, ya da en azından ben kabul olurdu.
Açık bir şey mi kaçırıyorum yoksa aptalca bir şey mi yapıyorum? Hem makul olasılıkla C benim ikinci dil gereğidir gibidir uzak . Bunun şimdi biraz garip olması garip ve derleyicinin ne yapmaya çalıştığını anlayamıyorum.
GÜNCELLEME : Bu, tek başına uzun olarak yeniden rağmen Talimatlar, bu çoğaltmak debian:testing
liman işçisi konteyner vardır gcc-10
at gcc-10.0.1
. Ayrıca, bana güvenmiyorsanız , sadece bu komutları çalıştırmayın .
Maalesef bu minimal tekrarlanabilir bir örnek değil.
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
Sonra R konsolunda, programı çalıştırmaya run
başlamak için yazdıktan sonra 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)
GDB'de inceleme oldukça hızlı bir şekilde (doğru anlarsam)
CSR_strmlen_x
başlatılmayan dizeye erişmeye çalıştığını gösterir.
GÜNCELLEME 2 : Bu oldukça özyinelemeli bir işlevdir ve bunun üzerine dize başlatma biti birçok kez çağrılır. Bu çoğunlukla b / c tembel oluyordum, sadece özyinelemede rapor etmek istediğimiz bir şeyle karşılaştığımızda bir kez başlatılmış dizelere ihtiyacımız var, ancak bir şeyle karşılaşmak mümkün olduğunda her zaman başlatmak daha kolaydı. Bundan bahsedeceğim çünkü daha sonra göreceğiniz şey çoklu başlatmalar gösteriyor, ancak bunlardan sadece biri (muhtemelen <0x1400000001> adresine sahip) kullanılıyor.
Burada gösterdiğim şeylerin doğrudan segfault'a neden olan öğeyle ilişkili olduğunu garanti edemiyorum (aynı yasadışı adres erişimi olsa da), ancak @ nate-eldredge'nin sorduğu dizi öğesinin olmadığını gösteriyor dönüşten hemen önce veya çağrı işlevinde dönüşten hemen sonra başlatıldı. Arama işlevinin bunlardan 8'ini başlattığını ve hepsini çöp veya erişilemeyen bellekle doldurduğuna dikkat edin.
GÜNCELLEME 3 , söz konusu fonksiyonun sökülmesi:
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.
GÜNCELLEME 4 :
Yani, burada standardı ayrıştırmaya çalışmak, onun ilgili gibi görünen kısımlarıdır ( C11 taslağı ):
6.3.2.3 Par7 Dönüşümleri> Diğer İşlenenler> İşaretçiler
Bir nesne türüne işaretçi, farklı bir nesne türüne işaretçi olarak dönüştürülebilir. Elde edilen işaretçi başvurulan tür için doğru hizalanmamışsa 68), davranış tanımsızdır.
Aksi takdirde, tekrar dönüştürüldüğünde, sonuç orijinal işaretçiye eşittir. Nesneye yönelik bir işaretçi bir karakter türüne işaretçiye dönüştürüldüğünde, sonuç nesnenin adreslenen en düşük baytını gösterir. Sonucun art arda artması, nesnenin boyutuna kadar, nesnenin kalan baytlarına işaretçiler verir.
6.5 Par6 İfadeleri
Bir nesnenin depolanan değerine erişim için etkili türü, varsa, bildirilen nesnenin türüdür. 87) Karakter türü olmayan bir türe sahip bir lvalue aracılığıyla bildirilmiş bir türü olmayan bir nesneye bir değer depolanırsa, lvalue türü o erişim ve daha sonraki erişimler için nesnenin etkin türü haline gelir saklanan değeri değiştirin. Bir değer, memcpy veya memmove kullanılarak bildirilmiş bir türü olmayan bir nesneye kopyalanırsa veya bir karakter türü dizisi olarak kopyalanırsa, o erişim ve değeri değiştirmeyen sonraki erişimler için değiştirilen nesnenin etkin türü varsa, değerin kopyalandığı nesnenin etkin türü. Bildirilen türü olmayan bir nesneye yapılan tüm diğer erişimlerde, nesnenin etkili türü, erişim için kullanılan lvalue türüdür.
87) Ayrılan nesnelerin bildirilen türü yok.
IIUC , hizalanması garanti R_alloc
edilen bir malloc
ed bloğuna bir ofset döndürür ve ofsetten double
sonraki bloğun boyutu istenen boyuta gelir (R'ye özgü veriler için ofsetten önce ayırma da vardır). dönüşte R_alloc
bu işaretçiyi yayınlar (char *)
.
Bölüm 6.2.5 Par 29
Geçersiz bir işaretçi, bir karakter türüne işaretçi ile aynı temsil ve hizalama gereksinimlerine sahip olmalıdır. 48) Benzer şekilde, uyumlu tiplerin nitelikli veya niteliksiz versiyonlarına işaretçiler aynı temsil ve hizalama gerekliliklerine sahip olacaktır. Yapı türlerine yönelik tüm işaretçiler, birbirleriyle aynı temsil ve hizalama gereksinimlerine sahip olmalıdır.
Birlik tiplerine yönelik tüm göstergeler, birbirleriyle aynı temsil ve hizalama gereksinimlerine sahip olacaktır.
Diğer türlere işaretçiler aynı gösterim veya hizalama gereksinimlerine sahip olmak zorunda değildir.48) Aynı gösterim ve hizalama gereklilikleri, işlevler, değiştirilebilir işlevler ve işlev sendikaları değerleri arasında değişebilirlik anlamına gelir.
Soru Yani "biz recast izin vardır (char *)
için (const char **)
o sonuca ve yazma (const char **)
". Yukarıdakileri okuduğumda, kodun çalıştığı sistemlerde işaretçiler hizalama ile uyumlu double
hizalamaya sahip oldukları sürece , o zaman tamam.
"Katı takma adlandırma" yı ihlal ediyor muyuz? yani:
6.5 Par 7
Bir nesnenin depolanmış değerine yalnızca aşağıdaki türlerden birine sahip bir değer değeri ifadesiyle erişilir: 88)
- nesnenin etkili tipiyle uyumlu bir tip ...
88) Bu listenin amacı, bir nesnenin diğer adının örtüştüğü veya örtüştüğü durumları belirtmektir.
Peki, derleyici (veya ) ile gösterilen nesnenin etkili tipinin ne olduğunu düşünmelidir ? Muhtemelen beyan edilen tip mi , yoksa bu gerçekten belirsiz mi? Bana sadece bu durumda kapsamı aynı nesneye erişen başka bir 'değer' olmadığı için değil gibi geliyor.res.target
res.current
(const char **)
Standardın bu bölümlerinden anlam çıkarmak için güçlü bir şekilde mücadele ettiğimi itiraf edeceğim.
-mtune=native
makinenizin sahip olduğu CPU için optimize eder. Bu, farklı test kullanıcıları için farklı olacaktır ve sorunun bir parçası olabilir. Derlemeyi çalıştırırsanız, -v
makinenizde (örneğin -mtune=skylake
bilgisayarımda) hangi işlemci ailesinin olduğunu görebilmeniz gerekir .
disassemble
gdb içindeki talimatı da kullanabilirsiniz .