ฉันมีแพ็คเกจ R ที่มีโค้ดที่คอมไพล์ C ซึ่งค่อนข้างเสถียรมาระยะหนึ่งแล้วและได้รับการทดสอบกับแพลตฟอร์มและคอมไพเลอร์ที่หลากหลาย (windows / osx / debian / fedora gcc / clang)
เมื่อเร็ว ๆ นี้มีการเพิ่มแพลตฟอร์มใหม่เพื่อทดสอบแพคเกจอีกครั้ง:
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"
ณ จุดที่โค้ดที่คอมไพล์เริ่มต้นทันทีทำการแบ่งส่วนข้อมูลตามบรรทัดเหล่านี้:
*** caught segfault ***
address 0x1d00000001, cause 'memory not mapped'
ฉันสามารถสร้าง segfault อย่างสม่ำเสมอโดยใช้rocker/r-base
container docker พร้อมgcc-10.0.1
กับระดับการปรับให้-O2
เหมาะสม การใช้การเพิ่มประสิทธิภาพที่ต่ำกว่าจะกำจัดปัญหาได้ เรียกใช้การตั้งค่าอื่น ๆ รวมถึงภายใต้ valgrind (ทั้ง -O0 และ -O2), UBSAN (gcc / clang) ไม่แสดงปัญหาใด ๆ เลย ฉันก็แน่ใจด้วยว่านี่เป็นสิ่งที่เกิดขึ้นgcc-10.0.0
แต่ไม่มีข้อมูล
ฉันใช้gcc-10.0.1 -O2
เวอร์ชันด้วยgdb
และสังเกตเห็นบางสิ่งที่ดูแปลกสำหรับฉัน:
ในขณะที่ก้าวผ่านส่วนที่ไฮไลต์จะปรากฏการเริ่มต้นขององค์ประกอบที่สองของอาร์เรย์ถูกข้ามไป ( R_alloc
เป็นเสื้อคลุมรอบ ๆmalloc
ขยะตัวเองที่รวบรวมเมื่อกลับการควบคุมไปที่ R; segfault เกิดขึ้นก่อนที่จะกลับไป R) หลังจากนั้นโปรแกรมจะทำงานล้มเหลวเมื่อมีการเข้าถึงองค์ประกอบที่ไม่ได้กำหนดค่าเริ่มต้น (ในรุ่น gcc.10.0.1 -O2)
ฉันแก้ไขสิ่งนี้โดยเริ่มต้นองค์ประกอบที่เป็นปัญหาอย่างชัดเจนทุกที่ในรหัสที่นำไปสู่การใช้งานองค์ประกอบ แต่จริงๆแล้วควรได้รับการเริ่มต้นเป็นสตริงว่างหรืออย่างน้อยนั่นคือสิ่งที่ฉันจะสันนิษฐาน
ฉันขาดอะไรที่ชัดเจนหรือทำอะไรโง่ ๆ ? ทั้งสองมีแนวโน้มที่สมเหตุสมผลเนื่องจาก C เป็นภาษาที่สองของฉันในตอนนี้ มันแปลกมากที่ตอนนี้ครอบตัดหมดแล้วและฉันไม่สามารถเข้าใจได้ว่าคอมไพเลอร์พยายามทำอะไร
UPDATE : คำแนะนำในการทำซ้ำนี้แม้ว่านี้จะทำซ้ำตราบdebian:testing
ภาชนะนักเทียบท่ามีที่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)
การตรวจสอบใน gdb แสดงให้เห็นอย่างรวดเร็ว (ถ้าฉันเข้าใจถูกต้อง) ว่า
CSR_strmlen_x
พยายามเข้าถึงสตริงที่ไม่ได้กำหนดค่าเริ่มต้น
UPDATE 2 : นี่เป็นฟังก์ชั่นแบบเรียกซ้ำสูงและยิ่งไปกว่านั้นบิตการเริ่มต้นสตริงได้รับการเรียกหลายครั้ง นี่คือ b / c ส่วนใหญ่ที่ฉันขี้เกียจเราต้องการเพียงสตริงเริ่มต้นสำหรับครั้งเดียวที่เราพบสิ่งที่เราต้องการรายงานในการสอบถามซ้ำ แต่มันง่ายกว่าที่จะเริ่มต้นทุกครั้งที่เป็นไปได้ที่จะพบบางสิ่งบางอย่าง ฉันพูดถึงสิ่งนี้เพราะสิ่งที่คุณจะเห็นต่อไปจะแสดงการเริ่มต้นหลายรายการ แต่มีเพียงอันเดียวเท่านั้น (น่าจะเป็นอันแรกที่มีที่อยู่ <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) หากค่าถูกเก็บไว้ในวัตถุที่ไม่มีประเภทประกาศผ่าน lvalue ที่มีประเภทที่ไม่ใช่ประเภทตัวอักษรแล้วประเภทของ lvalue จะกลายเป็นชนิดที่มีประสิทธิภาพของวัตถุสำหรับการเข้าถึงนั้นและการเข้าถึงต่อมาที่ไม่ได้ ปรับเปลี่ยนค่าที่เก็บไว้ หากค่าถูกคัดลอกไปยังวัตถุที่ไม่มีประเภทที่ประกาศโดยใช้ memcpy หรือ memmove หรือถูกคัดลอกเป็นอาร์เรย์ประเภทอักขระดังนั้นชนิดที่มีประสิทธิภาพของวัตถุที่ถูกปรับเปลี่ยนสำหรับการเข้าถึงนั้นและสำหรับการเข้าถึงในภายหลังที่ไม่ได้แก้ไขค่านั้นคือ ชนิดที่มีประสิทธิภาพของวัตถุซึ่งค่าถูกคัดลอกถ้ามี สำหรับการเข้าถึงวัตถุอื่นที่ไม่มีประเภทที่ประกาศไว้ชนิดของวัตถุที่มีประสิทธิภาพนั้นเป็นเพียงประเภทของ lvalue ที่ใช้สำหรับการเข้าถึง
87) วัตถุที่จัดสรรไม่มีชนิดที่ประกาศไว้
IIUC R_alloc
ส่งคืนออฟเซ็ตเป็นmalloc
บล็อก ed ที่รับประกันว่าจะdouble
จัดตำแหน่งและขนาดของบล็อกหลังจากออฟเซ็ตมีขนาดที่ร้องขอ (นอกจากนี้ยังมีการจัดสรรก่อนออฟเซ็ตสำหรับข้อมูลเฉพาะ R) R_alloc
ปลดเปลื้องตัวชี้ไป(char *)
ที่กลับ
มาตรา 6.2.5 พาร์ 29
ตัวชี้ไปยังโมฆะจะต้องมีการเป็นตัวแทนและความต้องการการจัดตำแหน่งเช่นเดียวกับตัวชี้ไปยังประเภทตัวละคร 48) ในทำนองเดียวกันพอยน์เตอร์สำหรับประเภทที่เข้ากันได้หรือไม่ผ่านการรับรองของประเภทที่เข้ากันได้จะต้องมีการเป็นตัวแทนและความต้องการการจัดตำแหน่งเดียวกัน ตัวชี้ไปยังประเภทโครงสร้างทั้งหมดจะต้องมีข้อกำหนดการเป็นตัวแทนและการจัดตำแหน่งที่เหมือนกัน
พอยน์เตอร์ทั้งหมดไปยังประเภทยูเนี่ยนจะต้องมีข้อกำหนดการเป็นตัวแทนและการจัดตำแหน่งที่เหมือนกัน
ตัวชี้ไปยังประเภทอื่นไม่จำเป็นต้องมีข้อกำหนดการเป็นตัวแทนหรือการจัดตำแหน่งเดียวกัน48) ข้อกำหนดการเป็นตัวแทนและการจัดตำแหน่งเดียวกันมีขึ้นเพื่อบอกนัยถึงการแทนกันของฟังก์ชันการคืนค่าจากฟังก์ชันและสมาชิกของสหภาพ
ดังนั้นคำถามคือ "เราได้รับอนุญาตให้หล่อ(char *)
ไป(const char **)
และเขียนถึงว่ามันเป็น(const char **)
" การอ่านข้างต้นของฉันคือตราบใดที่พอยน์เตอร์ในระบบที่โค้ดทำงานนั้นมีการจัดตำแหน่งที่เข้ากันได้กับdouble
การจัดตำแหน่งจากนั้นก็ไม่เป็นไร
เราละเมิด "นามแฝงที่เข้มงวด" หรือไม่ เช่น:
6.5 พาร์ 7
วัตถุต้องมีค่าที่เก็บไว้เข้าถึงได้โดยนิพจน์ lvalue ที่มีประเภทใดประเภทหนึ่งต่อไปนี้: 88)
- ชนิดที่เข้ากันได้กับชนิดของวัตถุที่มีประสิทธิภาพ ...
88) ความตั้งใจของรายการนี้คือการระบุสถานการณ์เหล่านั้นซึ่งวัตถุอาจหรืออาจไม่ได้รับนามแฝง
ดังนั้นคอมไพเลอร์ควรคิดอย่างไรกับชนิดของวัตถุที่มีประสิทธิภาพซึ่งชี้โดยres.target
(หรือres.current
) คืออะไร? น่าจะเป็นประเภทที่ประกาศไว้(const char **)
หรือเป็นจริงที่คลุมเครือ? ฉันรู้สึกว่ามันไม่ใช่ในกรณีนี้เท่านั้นเพราะไม่มี 'lvalue' อื่น ๆ ในขอบเขตที่เข้าถึงวัตถุเดียวกัน
ฉันจะยอมรับว่าฉันกำลังดิ้นรนอย่างยิ่งยวดที่จะดึงความรู้สึกจากส่วนเหล่านี้ของมาตรฐาน
-mtune=native
ปรับแต่งสำหรับ CPU เฉพาะที่เครื่องของคุณมี นั่นจะแตกต่างกันสำหรับผู้ทดสอบที่แตกต่างกันและอาจเป็นส่วนหนึ่งของปัญหา หากคุณเรียกใช้การคอมไพล์ด้วย-v
คุณควรเห็นซีพียูตระกูลใดที่อยู่บนเครื่องของคุณ (เช่น-mtune=skylake
บนคอมพิวเตอร์ของฉัน)
disassemble
คำสั่งใน gdb