@@ -1090,6 +1090,8 @@ fn task_can_overflow(
10901090 let data = std:: fs:: read ( f) . context ( "could not open ELF file" ) ?;
10911091 let elf = goblin:: elf:: Elf :: parse ( & data) ?;
10921092
1093+ // Read the .stack_sizes section, which is an array of
1094+ // `(address: u32, stack size: unsigned leb128)` tuples
10931095 let sizes = crate :: elf:: get_section_by_name ( & elf, ".stack_sizes" )
10941096 . context ( "could not get .stack_sizes" ) ?;
10951097 let mut sizes = & data[ sizes. sh_offset as usize ..] [ ..sizes. sh_size as usize ] ;
@@ -1102,18 +1104,9 @@ fn task_can_overflow(
11021104 addr_to_frame_size. insert ( addr, size) ;
11031105 }
11041106
1105- let text = crate :: elf:: get_section_by_name ( & elf, ".text" )
1106- . context ( "could not get .text" ) ?;
1107-
1108- // Pack everything into a single data structure
1109- #[ derive( Debug ) ]
1110- struct FunctionData {
1111- name : String ,
1112- short_name : String ,
1113- frame_size : Option < u64 > ,
1114- calls : BTreeSet < u32 > ,
1115- }
1116-
1107+ // There are `$t` and `$d` symbols which indicate the beginning of text
1108+ // versus data in the `.text` region. We collect them into a `BTreeMap`
1109+ // here so that we can avoid trying to decode inline data words.
11171110 let mut text_regions = BTreeMap :: new ( ) ;
11181111 for sym in elf. syms . iter ( ) {
11191112 if sym. st_name == 0
@@ -1123,11 +1116,7 @@ fn task_can_overflow(
11231116 continue ;
11241117 }
11251118
1126- // Clear the lowest bit, which indicates that the function contains
1127- // thumb instructions (always true for our systems!)
1128- let val = sym. st_value ;
1129- let addr = val as u32 ;
1130-
1119+ let addr = sym. st_value as u32 ;
11311120 let is_text = match elf. strtab . get_at ( sym. st_name ) {
11321121 Some ( "$t" ) => true ,
11331122 Some ( "$d" ) => false ,
@@ -1138,6 +1127,22 @@ fn task_can_overflow(
11381127 } ;
11391128 text_regions. insert ( addr, is_text) ;
11401129 }
1130+ let is_code = |addr| {
1131+ let mut iter = text_regions. range ( ..=addr) ;
1132+ * iter. next_back ( ) . unwrap ( ) . 1
1133+ } ;
1134+
1135+ // We'll be packing everything into this data structure
1136+ #[ derive( Debug ) ]
1137+ struct FunctionData {
1138+ name : String ,
1139+ short_name : String ,
1140+ frame_size : Option < u64 > ,
1141+ calls : BTreeSet < u32 > ,
1142+ }
1143+
1144+ let text = crate :: elf:: get_section_by_name ( & elf, ".text" )
1145+ . context ( "could not get .text" ) ?;
11411146
11421147 use capstone:: {
11431148 arch:: { arm, ArchOperand , BuildsCapstone , BuildsCapstoneExtraMode } ,
@@ -1151,17 +1156,16 @@ fn task_can_overflow(
11511156 . build ( )
11521157 . map_err ( |e| anyhow ! ( "failed to initialize disassembler: {e:?}" ) ) ?;
11531158
1159+ // Disassemble each function, building a map of its call sites
11541160 let mut fns = BTreeMap :: new ( ) ;
11551161 for sym in elf. syms . iter ( ) {
1162+ // We only care about named function symbols here
11561163 if sym. st_name == 0 || !sym. is_function ( ) || sym. st_size == 0 {
11571164 continue ;
11581165 }
11591166
1160- let name = match elf. strtab . get_at ( sym. st_name ) {
1161- Some ( n) => n,
1162- None => {
1163- bail ! ( "bad symbol in {task_name}: {}" , sym. st_name) ;
1164- }
1167+ let Some ( name) = elf. strtab . get_at ( sym. st_name ) else {
1168+ bail ! ( "bad symbol in {task_name}: {}" , sym. st_name) ;
11651169 } ;
11661170
11671171 // Clear the lowest bit, which indicates that the function contains
@@ -1178,21 +1182,13 @@ fn task_can_overflow(
11781182 let mut chunk = None ;
11791183 for ( i, b) in text. iter ( ) . enumerate ( ) {
11801184 let addr = base_addr + i as u32 ;
1181- let mut iter = text_regions. range ( ..=addr) ;
1182- // Check if this is a code or data byte
1183- if * iter. next_back ( ) . unwrap ( ) . 1 {
1184- if chunk. is_none ( ) {
1185- chunk = Some ( ( addr, vec ! [ ] ) ) ;
1186- }
1187- chunk. as_mut ( ) . unwrap ( ) . 1 . push ( * b) ;
1188- } else if let Some ( c) = chunk. take ( ) {
1189- chunks. push ( c) ;
1185+ if is_code ( addr) {
1186+ chunk. get_or_insert ( ( addr, vec ! [ ] ) ) . 1 . push ( * b) ;
1187+ } else {
1188+ chunks. extend ( chunk. take ( ) ) ;
11901189 }
11911190 }
1192- // Process data in the trailing chunk
1193- if let Some ( c) = chunk {
1194- chunks. push ( c) ;
1195- }
1191+ chunks. extend ( chunk) ; // don't forget the trailing chunk!
11961192
11971193 let mut calls = BTreeSet :: new ( ) ;
11981194 for ( addr, chunk) in chunks {
@@ -1254,12 +1250,6 @@ fn task_can_overflow(
12541250 ) ;
12551251 }
12561252
1257- let start_addr = fns
1258- . iter ( )
1259- . find ( |( _addr, v) | v. name . as_str ( ) == "_start" )
1260- . map ( |( addr, _v) | * addr)
1261- . ok_or_else ( || anyhow ! ( "could not find _start" ) ) ?;
1262-
12631253 fn recurse (
12641254 call_stack : & mut Vec < u32 > ,
12651255 recurse_depth : usize ,
@@ -1293,7 +1283,8 @@ fn task_can_overflow(
12931283 }
12941284 for j in & f. calls {
12951285 if call_stack. contains ( j) {
1296- // Skip recurse calls, because we can't resolve them
1286+ // Skip recursive / mutually recursive calls, because we can't
1287+ // reason about them.
12971288 continue ;
12981289 } else {
12991290 call_stack. push ( * j) ;
@@ -1309,11 +1300,20 @@ fn task_can_overflow(
13091300 }
13101301 }
13111302 }
1312- let mut deepest = None ;
1303+
1304+ // Find stack sizes by traversing the graph
13131305 if verbose {
13141306 println ! ( "finding stack sizes for {task_name}" ) ;
13151307 }
1308+ let start_addr = fns
1309+ . iter ( )
1310+ . find ( |( _addr, v) | v. name . as_str ( ) == "_start" )
1311+ . map ( |( addr, _v) | * addr)
1312+ . ok_or_else ( || anyhow ! ( "could not find _start" ) ) ?;
1313+ let mut deepest = None ;
13161314 recurse ( & mut vec ! [ start_addr] , 0 , 0 , & fns, & mut deepest, verbose) ;
1315+
1316+ // Check against our configured task stack size
13171317 let Some ( ( max_depth, max_stack) ) = deepest else {
13181318 unreachable ! ( "must have at least one call stack" ) ;
13191319 } ;
0 commit comments