@@ -8,25 +8,26 @@ use std::{hash::DefaultHasher, ops::Range};
88
99pub const UNWIND_FILE_EXT : & str = "unwind_data" ;
1010
11- pub type UnwindData = UnwindDataV3 ;
11+ pub type UnwindData = UnwindDataV4 ;
1212impl UnwindData {
1313 pub fn parse ( reader : & [ u8 ] ) -> anyhow:: Result < Self > {
1414 let compat: UnwindDataCompat = bincode:: deserialize ( reader) ?;
1515
1616 match compat {
1717 UnwindDataCompat :: V1 ( _) => {
18- anyhow:: bail!( "Cannot parse V1 unwind data as V3 (breaking changes)" )
18+ anyhow:: bail!( "Cannot parse V1 unwind data as V4 (breaking changes)" )
1919 }
2020 UnwindDataCompat :: V2 ( _) => {
21- anyhow:: bail!( "Cannot parse V2 unwind data as V3 (breaking changes)" )
21+ anyhow:: bail!( "Cannot parse V2 unwind data as V4 (breaking changes)" )
2222 }
23- UnwindDataCompat :: V3 ( v3) => Ok ( v3) ,
23+ UnwindDataCompat :: V3 ( v3) => Ok ( v3. into ( ) ) ,
24+ UnwindDataCompat :: V4 ( v4) => Ok ( v4) ,
2425 }
2526 }
2627
2728 pub fn save_to < P : AsRef < std:: path:: Path > > ( & self , folder : P , key : & str ) -> anyhow:: Result < ( ) > {
2829 let path = folder. as_ref ( ) . join ( format ! ( "{key}.{UNWIND_FILE_EXT}" ) ) ;
29- let compat = UnwindDataCompat :: V3 ( self . clone ( ) ) ;
30+ let compat = UnwindDataCompat :: V4 ( self . clone ( ) ) ;
3031 let file = std:: fs:: File :: create ( & path) ?;
3132 const BUFFER_SIZE : usize = 256 * 1024 ;
3233 let writer = BufWriter :: with_capacity ( BUFFER_SIZE , file) ;
@@ -41,6 +42,7 @@ enum UnwindDataCompat {
4142 V1 ( UnwindDataV1 ) ,
4243 V2 ( UnwindDataV2 ) ,
4344 V3 ( UnwindDataV3 ) ,
45+ V4 ( UnwindDataV4 ) ,
4446}
4547
4648#[ doc( hidden) ]
@@ -90,6 +92,9 @@ impl UnwindDataV2 {
9092 UnwindDataCompat :: V3 ( _) => {
9193 anyhow:: bail!( "Cannot parse V3 unwind data as V2 (missing per-pid fields)" )
9294 }
95+ UnwindDataCompat :: V4 ( _) => {
96+ anyhow:: bail!( "Cannot parse V4 unwind data as V2 (missing per-pid fields)" )
97+ }
9398 }
9499 }
95100}
@@ -135,6 +140,35 @@ impl From<UnwindDataV2> for UnwindDataV3 {
135140 }
136141}
137142
143+ /// Pid-agnostic unwind data with an optional `.eh_frame_hdr`.
144+ ///
145+ /// The hdr is only a binary-search index into `.eh_frame` — some binaries
146+ /// (e.g. Valgrind's statically-linked tools) are linked without
147+ /// `ld --eh-frame-hdr` and don't carry it. The parser rebuilds the index from
148+ /// `.eh_frame` in that case.
149+ #[ derive( Serialize , Deserialize , Clone , PartialEq , Eq , Hash ) ]
150+ pub struct UnwindDataV4 {
151+ pub path : String ,
152+ pub base_svma : u64 ,
153+ pub eh_frame_hdr : Option < Vec < u8 > > ,
154+ pub eh_frame_hdr_svma : Option < Range < u64 > > ,
155+ pub eh_frame : Vec < u8 > ,
156+ pub eh_frame_svma : Range < u64 > ,
157+ }
158+
159+ impl From < UnwindDataV3 > for UnwindDataV4 {
160+ fn from ( v3 : UnwindDataV3 ) -> Self {
161+ Self {
162+ path : v3. path ,
163+ base_svma : v3. base_svma ,
164+ eh_frame_hdr : Some ( v3. eh_frame_hdr ) ,
165+ eh_frame_hdr_svma : Some ( v3. eh_frame_hdr_svma ) ,
166+ eh_frame : v3. eh_frame ,
167+ eh_frame_svma : v3. eh_frame_svma ,
168+ }
169+ }
170+ }
171+
138172impl Debug for UnwindDataV2 {
139173 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
140174 let eh_frame_hdr_hash = {
@@ -192,6 +226,33 @@ impl Debug for UnwindDataV3 {
192226 }
193227}
194228
229+ impl Debug for UnwindDataV4 {
230+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
231+ let eh_frame_hdr_hash = self . eh_frame_hdr . as_ref ( ) . map ( |eh_frame_hdr| {
232+ let mut hasher = DefaultHasher :: new ( ) ;
233+ eh_frame_hdr. hash ( & mut hasher) ;
234+ hasher. finish ( )
235+ } ) ;
236+ let eh_frame_hash = {
237+ let mut hasher = DefaultHasher :: new ( ) ;
238+ self . eh_frame . hash ( & mut hasher) ;
239+ hasher. finish ( )
240+ } ;
241+
242+ f. debug_struct ( "UnwindData" )
243+ . field ( "path" , & self . path )
244+ . field ( "base_svma" , & format_args ! ( "{:x}" , self . base_svma) )
245+ . field (
246+ "eh_frame_hdr_svma" ,
247+ & format_args ! ( "{:x?}" , self . eh_frame_hdr_svma) ,
248+ )
249+ . field ( "eh_frame_hdr_hash" , & format_args ! ( "{eh_frame_hdr_hash:x?}" ) )
250+ . field ( "eh_frame_hash" , & format_args ! ( "{eh_frame_hash:x}" ) )
251+ . field ( "eh_frame_svma" , & format_args ! ( "{:x?}" , self . eh_frame_svma) )
252+ . finish ( )
253+ }
254+ }
255+
195256/// Per-pid mounting info referencing a deduplicated unwind data entry.
196257#[ derive( Debug , Serialize , Deserialize , Clone ) ]
197258pub struct MappedProcessUnwindData {
@@ -223,6 +284,7 @@ mod tests {
223284
224285 const V2_BINARY : & [ u8 ] = include_bytes ! ( "../testdata/unwind_data_v2.bin" ) ;
225286 const V3_BINARY : & [ u8 ] = include_bytes ! ( "../testdata/unwind_data_v3.bin" ) ;
287+ const V4_BINARY : & [ u8 ] = include_bytes ! ( "../testdata/unwind_data_v4.bin" ) ;
226288
227289 fn create_sample_v2 ( ) -> UnwindDataV2 {
228290 UnwindDataV2 {
@@ -249,18 +311,38 @@ mod tests {
249311 }
250312 }
251313
314+ fn create_sample_v4 ( ) -> UnwindDataV4 {
315+ UnwindDataV4 {
316+ path : "/lib/test.so" . to_string ( ) ,
317+ base_svma : 0x0 ,
318+ // No `.eh_frame_hdr`, like Valgrind's statically-linked tools
319+ eh_frame_hdr : None ,
320+ eh_frame_hdr_svma : None ,
321+ eh_frame : vec ! [ 5 , 6 , 7 , 8 ] ,
322+ eh_frame_svma : 0x200 ..0x300 ,
323+ }
324+ }
325+
252326 #[ test]
253- fn test_parse_v2_as_v3_should_error ( ) {
254- // Try to parse V2 binary artifact as V3 using UnwindData::parse
255- let result = UnwindDataV3 :: parse ( V2_BINARY ) ;
327+ #[ ignore = "one-off generator for the V4 testdata artifact" ]
328+ fn generate_v4_testdata ( ) {
329+ let compat = UnwindDataCompat :: V4 ( create_sample_v4 ( ) ) ;
330+ let bytes = bincode:: serialize ( & compat) . unwrap ( ) ;
331+ std:: fs:: write ( "testdata/unwind_data_v4.bin" , bytes) . unwrap ( ) ;
332+ }
256333
257- // Should error due to breaking changes between V2 and V3
334+ #[ test]
335+ fn test_parse_v2_as_v4_should_error ( ) {
336+ // Try to parse V2 binary artifact as V4 using UnwindData::parse
337+ let result = UnwindData :: parse ( V2_BINARY ) ;
338+
339+ // Should error due to breaking changes between V2 and V4
258340 assert ! ( result. is_err( ) ) ;
259341 let err = result. unwrap_err ( ) ;
260342 assert ! (
261343 err. to_string( )
262- . contains( "Cannot parse V2 unwind data as V3 " ) ,
263- "Expected error message about V2->V3 incompatibility, got: {err}"
344+ . contains( "Cannot parse V2 unwind data as V4 " ) ,
345+ "Expected error message about V2->V4 incompatibility, got: {err}"
264346 ) ;
265347 }
266348
@@ -280,13 +362,39 @@ mod tests {
280362 }
281363
282364 #[ test]
283- fn test_parse_v3_as_v3 ( ) {
284- // Parse V3 binary artifact as V3 using UnwindData::parse
285- let parsed_v3 = UnwindData :: parse ( V3_BINARY ) . expect ( "Failed to parse V3 data as V3" ) ;
365+ fn test_parse_v4_as_v2_should_error ( ) {
366+ // Try to parse V4 binary artifact as V2 using UnwindDataV2::parse
367+ let result = UnwindDataV2 :: parse ( V4_BINARY ) ;
368+
369+ // Should error with specific message about missing per-pid fields
370+ assert ! ( result. is_err( ) ) ;
371+ let err = result. unwrap_err ( ) ;
372+ assert ! (
373+ err. to_string( )
374+ . contains( "Cannot parse V4 unwind data as V2" ) ,
375+ "Expected error message about V4->V2 incompatibility, got: {err}"
376+ ) ;
377+ }
378+
379+ #[ test]
380+ fn test_parse_v3_as_v4 ( ) {
381+ // Parse V3 binary artifact using UnwindData::parse — it converts to V4
382+ let parsed = UnwindData :: parse ( V3_BINARY ) . expect ( "Failed to parse V3 data as V4" ) ;
383+
384+ // Should match the V3 data with the hdr fields wrapped in `Some`
385+ let expected: UnwindDataV4 = create_sample_v3 ( ) . into ( ) ;
386+ assert_eq ! ( parsed, expected) ;
387+ assert ! ( parsed. eh_frame_hdr. is_some( ) ) ;
388+ }
389+
390+ #[ test]
391+ fn test_parse_v4_as_v4 ( ) {
392+ // Parse V4 binary artifact as V4 using UnwindData::parse
393+ let parsed_v4 = UnwindData :: parse ( V4_BINARY ) . expect ( "Failed to parse V4 data as V4" ) ;
286394
287- // Should match expected V3 data
288- let expected_v3 = create_sample_v3 ( ) ;
289- assert_eq ! ( parsed_v3 , expected_v3 ) ;
395+ // Should match expected V4 data (without an eh_frame_hdr)
396+ let expected_v4 = create_sample_v4 ( ) ;
397+ assert_eq ! ( parsed_v4 , expected_v4 ) ;
290398 }
291399
292400 #[ test]
0 commit comments