From ee25b6bdebf3bd079d4129470c31b803c21bdedc Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Thu, 24 Jul 2014 11:09:41 +0200 Subject: [PATCH 1/4] debuginfo: Allow to activate GDB pretty printers in autotests. --- src/compiletest/runtest.rs | 73 ++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs index b36bc96cd35..881eee93d2a 100644 --- a/src/compiletest/runtest.rs +++ b/src/compiletest/runtest.rs @@ -323,7 +323,12 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { }; let config = &mut config; - let DebuggerCommands { commands, check_lines, .. } = parse_debugger_commands(testfile, "gdb"); + let DebuggerCommands { + commands, + check_lines, + use_gdb_pretty_printer, + .. + } = parse_debugger_commands(testfile, "gdb"); let mut cmds = commands.connect("\n"); // compile test file (it should have 'compile-flags:-g' in the header) @@ -334,7 +339,6 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { let exe_file = make_exe_name(config, testfile); - let mut proc_args; let debugger_run_result; match config.target.as_slice() { "arm-linux-androideabi" => { @@ -454,6 +458,12 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { } _=> { + let rust_src_root = find_rust_src_root(config).expect("Could not find Rust source root"); + let rust_pp_module_rel_path = Path::new("./src/etc"); + let rust_pp_module_abs_path = rust_src_root.join(rust_pp_module_rel_path) + .as_str() + .unwrap() + .to_string(); // write debugger script let script_str = [ "set charset UTF-8".to_string(), @@ -466,6 +476,12 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { script_str.as_slice(), "debugger.script"); + if use_gdb_pretty_printer { + // Only emit the gdb auto-loading script if pretty printers + // should actually be loaded + dump_gdb_autoload_script(config, testfile); + } + // run debugger script with gdb #[cfg(windows)] fn debugger() -> String { @@ -483,16 +499,27 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { vec!("-quiet".to_string(), "-batch".to_string(), "-nx".to_string(), + // Add the directory containing the pretty printers to + // GDB's script auto loading safe path ... + format!("-iex=add-auto-load-safe-path {}", + rust_pp_module_abs_path.as_slice()), + // ... and also the test directory + format!("-iex=add-auto-load-safe-path {}", + config.build_base.as_str().unwrap()), format!("-command={}", debugger_script.as_str().unwrap()), exe_file.as_str().unwrap().to_string()); - proc_args = ProcArgs { + + let proc_args = ProcArgs { prog: debugger(), args: debugger_opts, }; + + let environment = vec![("PYTHONPATH".to_string(), rust_pp_module_abs_path)]; + debugger_run_result = compose_and_run(config, testfile, proc_args, - Vec::new(), + environment, config.run_lib_path.as_slice(), None, None); @@ -504,6 +531,32 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { } check_debugger_output(&debugger_run_result, check_lines.as_slice()); + + fn dump_gdb_autoload_script(config: &Config, testfile: &Path) { + let mut script_path = output_base_name(config, testfile); + let mut script_file_name = script_path.filename().unwrap().to_vec(); + script_file_name.push_all("-gdb.py".as_bytes()); + script_path.set_filename(script_file_name.as_slice()); + + let script_content = "import gdb_rust_pretty_printing\n\ + gdb_rust_pretty_printing.register_printers(gdb.current_objfile())\n" + .as_bytes(); + + File::create(&script_path).write(script_content).unwrap(); + } +} + +fn find_rust_src_root(config: &Config) -> Option { + let mut path = config.src_base.clone(); + let path_postfix = Path::new("src/etc/lldb_batchmode.py"); + + while path.pop() { + if path.join(path_postfix.clone()).is_file() { + return Some(path); + } + } + + return None; } fn run_debuginfo_lldb_test(config: &Config, props: &TestProps, testfile: &Path) { @@ -533,7 +586,8 @@ fn run_debuginfo_lldb_test(config: &Config, props: &TestProps, testfile: &Path) let DebuggerCommands { commands, check_lines, - breakpoint_lines + breakpoint_lines, + .. } = parse_debugger_commands(testfile, "lldb"); // Write debugger script: @@ -619,6 +673,7 @@ struct DebuggerCommands { commands: Vec, check_lines: Vec, breakpoint_lines: Vec, + use_gdb_pretty_printer: bool } fn parse_debugger_commands(file_path: &Path, debugger_prefix: &str) @@ -631,6 +686,7 @@ fn parse_debugger_commands(file_path: &Path, debugger_prefix: &str) let mut breakpoint_lines = vec!(); let mut commands = vec!(); let mut check_lines = vec!(); + let mut use_gdb_pretty_printer = false; let mut counter = 1; let mut reader = BufferedReader::new(File::open(file_path).unwrap()); for line in reader.lines() { @@ -640,6 +696,10 @@ fn parse_debugger_commands(file_path: &Path, debugger_prefix: &str) breakpoint_lines.push(counter); } + if line.as_slice().contains("gdb-use-pretty-printer") { + use_gdb_pretty_printer = true; + } + header::parse_name_value_directive( line.as_slice(), command_directive.as_slice()).map(|cmd| { @@ -663,7 +723,8 @@ fn parse_debugger_commands(file_path: &Path, debugger_prefix: &str) DebuggerCommands { commands: commands, check_lines: check_lines, - breakpoint_lines: breakpoint_lines + breakpoint_lines: breakpoint_lines, + use_gdb_pretty_printer: use_gdb_pretty_printer, } } From 6974b4f1b52556e191ade6653e9a9440d1d19610 Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Wed, 30 Jul 2014 15:56:42 +0200 Subject: [PATCH 2/4] debuginfo: Add GDB pretty printers for structs and enums. --- src/compiletest/runtest.rs | 3 +- src/etc/gdb_rust_pretty_printing.py | 225 ++++++++++++++++++ src/librustc/middle/trans/debuginfo.rs | 25 +- src/librustc_llvm/lib.rs | 5 +- .../debuginfo/gdb-pretty-struct-and-enums.rs | 167 +++++++++++++ 5 files changed, 422 insertions(+), 3 deletions(-) create mode 100644 src/etc/gdb_rust_pretty_printing.py create mode 100644 src/test/debuginfo/gdb-pretty-struct-and-enums.rs diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs index 881eee93d2a..27f6fbcf9f6 100644 --- a/src/compiletest/runtest.rs +++ b/src/compiletest/runtest.rs @@ -458,7 +458,8 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { } _=> { - let rust_src_root = find_rust_src_root(config).expect("Could not find Rust source root"); + let rust_src_root = find_rust_src_root(config) + .expect("Could not find Rust source root"); let rust_pp_module_rel_path = Path::new("./src/etc"); let rust_pp_module_abs_path = rust_src_root.join(rust_pp_module_rel_path) .as_str() diff --git a/src/etc/gdb_rust_pretty_printing.py b/src/etc/gdb_rust_pretty_printing.py new file mode 100644 index 00000000000..c84dde92f8f --- /dev/null +++ b/src/etc/gdb_rust_pretty_printing.py @@ -0,0 +1,225 @@ +# Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +# file at the top-level directory of this distribution and at +# http://rust-lang.org/COPYRIGHT. +# +# Licensed under the Apache License, Version 2.0 or the MIT license +# , at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +import gdb + +#=============================================================================== +# GDB Pretty Printing Module for Rust +#=============================================================================== + +def register_printers(objfile): + "Registers Rust pretty printers for the given objfile" + objfile.pretty_printers.append(rust_pretty_printer_lookup_function) + +def rust_pretty_printer_lookup_function(val): + "Returns the correct Rust pretty printer for the given value if there is one" + type_code = val.type.code + + if type_code == gdb.TYPE_CODE_STRUCT: + struct_kind = classify_struct(val.type) + + if struct_kind == STRUCT_KIND_STR_SLICE: + return RustStringSlicePrinter(val) + + if struct_kind == STRUCT_KIND_TUPLE: + return RustTuplePrinter(val) + + if struct_kind == STRUCT_KIND_TUPLE_STRUCT: + return RustTupleStructPrinter(val, False) + + if struct_kind == STRUCT_KIND_CSTYLE_VARIANT: + return RustCStyleEnumPrinter(val[get_field_at_index(val, 0)]) + + if struct_kind == STRUCT_KIND_TUPLE_VARIANT: + return RustTupleStructPrinter(val, True) + + if struct_kind == STRUCT_KIND_STRUCT_VARIANT: + return RustStructPrinter(val, True) + + return RustStructPrinter(val, False) + + # Enum handling + if type_code == gdb.TYPE_CODE_UNION: + enum_members = list(val.type.fields()) + enum_member_count = len(enum_members) + + if enum_member_count == 0: + return RustStructPrinter(val, false) + + if enum_member_count == 1: + if enum_members[0].name == None: + # This is a singleton enum + return rust_pretty_printer_lookup_function(val[enum_members[0]]) + else: + assert enum_members[0].name.startswith("RUST$ENCODED$ENUM$") + # This is a space-optimized enum + last_separator_index = enum_members[0].name.rfind("$") + second_last_separator_index = first_variant_name.rfind("$", 0, last_separator_index) + disr_field_index = first_variant_name[second_last_separator_index + 1 : + last_separator_index] + disr_field_index = int(disr_field_index) + + sole_variant_val = val[enum_members[0]] + disr_field = get_field_at_index(sole_variant_val, disr_field_index) + discriminant = int(sole_variant_val[disr_field]) + + if discriminant == 0: + null_variant_name = first_variant_name[last_separator_index + 1:] + return IdentityPrinter(null_variant_name) + + return rust_pretty_printer_lookup_function(sole_variant_val) + + # This is a regular enum, extract the discriminant + discriminant_name, discriminant_val = extract_discriminant_value(val) + return rust_pretty_printer_lookup_function(val[enum_members[discriminant_val]]) + + + + return None + +#=------------------------------------------------------------------------------ +# Pretty Printer Classes +#=------------------------------------------------------------------------------ + +class RustStructPrinter: + def __init__(self, val, hide_first_field): + self.val = val + self.hide_first_field = hide_first_field + + def to_string(self): + return self.val.type.tag + + def children(self): + cs = [] + for field in self.val.type.fields(): + field_name = field.name; + if field_name == None: + field_name = "" + name_value_tuple = ( field_name, self.val[field] ) + cs.append( name_value_tuple ) + + if self.hide_first_field: + cs = cs[1:] + + return cs + +class RustTuplePrinter: + def __init__(self, val): + self.val = val + + def to_string(self): + return None + + def children(self): + cs = [] + for field in self.val.type.fields(): + cs.append( ("", self.val[field]) ) + + return cs + + def display_hint(self): + return "array" + +class RustTupleStructPrinter: + def __init__(self, val, hide_first_field): + self.val = val + self.hide_first_field = hide_first_field + + def to_string(self): + return self.val.type.tag + + def children(self): + cs = [] + for field in self.val.type.fields(): + cs.append( ("", self.val[field]) ) + + if self.hide_first_field: + cs = cs[1:] + + return cs + + def display_hint(self): + return "array" + +class RustStringSlicePrinter: + def __init__(self, val): + self.val = val + + def to_string(self): + slice_byte_len = self.val["length"] + return '"%s"' % self.val["data_ptr"].string(encoding = "utf-8", + length = slice_byte_len) + +class RustCStyleEnumPrinter: + def __init__(self, val): + assert val.type.code == gdb.TYPE_CODE_ENUM + self.val = val + + def to_string(self): + return str(self.val) + +class IdentityPrinter: + def __init__(self, string): + self.string + + def to_string(self): + return self.string + +STRUCT_KIND_REGULAR_STRUCT = 0 +STRUCT_KIND_TUPLE_STRUCT = 1 +STRUCT_KIND_TUPLE = 2 +STRUCT_KIND_TUPLE_VARIANT = 3 +STRUCT_KIND_STRUCT_VARIANT = 4 +STRUCT_KIND_CSTYLE_VARIANT = 5 +STRUCT_KIND_STR_SLICE = 6 + +def classify_struct(type): + if type.tag == "&str": + return STRUCT_KIND_STR_SLICE + + fields = list(type.fields()) + field_count = len(fields) + + if field_count == 0: + return STRUCT_KIND_REGULAR_STRUCT + + if fields[0].artificial: + if field_count == 1: + return STRUCT_KIND_CSTYLE_VARIANT + elif fields[1].name == None: + return STRUCT_KIND_TUPLE_VARIANT + else: + return STRUCT_KIND_STRUCT_VARIANT + + if fields[0].name == None: + if type.tag.startswith("("): + return STRUCT_KIND_TUPLE + else: + return STRUCT_KIND_TUPLE_STRUCT + + return STRUCT_KIND_REGULAR_STRUCT + +def extract_discriminant_value(enum_val): + assert enum_val.type.code == gdb.TYPE_CODE_UNION + for variant_descriptor in enum_val.type.fields(): + variant_val = enum_val[variant_descriptor] + for field in variant_val.type.fields(): + return (field.name, int(variant_val[field])) + +def first_field(val): + for field in val.type.fields(): + return field + +def get_field_at_index(val, index): + i = 0 + for field in val.type.fields(): + if i == index: + return field + return None \ No newline at end of file diff --git a/src/librustc/middle/trans/debuginfo.rs b/src/librustc/middle/trans/debuginfo.rs index 566f71220b0..fbe96c9f586 100644 --- a/src/librustc/middle/trans/debuginfo.rs +++ b/src/librustc/middle/trans/debuginfo.rs @@ -235,6 +235,9 @@ static UNKNOWN_COLUMN_NUMBER: c_uint = 0; static UNKNOWN_FILE_METADATA: DIFile = (0 as DIFile); static UNKNOWN_SCOPE_METADATA: DIScope = (0 as DIScope); +static FLAGS_NONE: c_uint = 0; +static FLAGS_ARTIFICAL: c_uint = llvm::debuginfo::FlagArtificial as c_uint; + //=----------------------------------------------------------------------------- // Public Interface of debuginfo module //=----------------------------------------------------------------------------- @@ -1733,6 +1736,7 @@ struct MemberDescription { llvm_type: Type, type_metadata: DIType, offset: MemberOffset, + flags: c_uint } // A factory for MemberDescriptions. It produces a list of member descriptions @@ -1891,6 +1895,7 @@ impl StructMemberDescriptionFactory { llvm_type: type_of::type_of(cx, field.mt.ty), type_metadata: type_metadata(cx, field.mt.ty, self.span), offset: offset, + flags: FLAGS_NONE, } }).collect() } @@ -1951,6 +1956,7 @@ impl TupleMemberDescriptionFactory { llvm_type: type_of::type_of(cx, component_type), type_metadata: type_metadata(cx, component_type, self.span), offset: ComputedMemberOffset, + flags: FLAGS_NONE, } }).collect() } @@ -2036,6 +2042,7 @@ impl EnumMemberDescriptionFactory { llvm_type: variant_llvm_type, type_metadata: variant_type_metadata, offset: FixedMemberOffset { bytes: 0 }, + flags: FLAGS_NONE } }).collect() }, @@ -2069,6 +2076,7 @@ impl EnumMemberDescriptionFactory { llvm_type: variant_llvm_type, type_metadata: variant_type_metadata, offset: FixedMemberOffset { bytes: 0 }, + flags: FLAGS_NONE } ] } @@ -2102,6 +2110,7 @@ impl EnumMemberDescriptionFactory { llvm_type: non_null_llvm_type, type_metadata: non_null_type_metadata, offset: FixedMemberOffset { bytes: 0 }, + flags: FLAGS_NONE }; let unique_type_id = debug_context(cx).type_map @@ -2139,6 +2148,7 @@ impl EnumMemberDescriptionFactory { llvm_type: artificial_struct_llvm_type, type_metadata: artificial_struct_metadata, offset: FixedMemberOffset { bytes: 0 }, + flags: FLAGS_NONE } ] }, @@ -2183,6 +2193,7 @@ impl EnumMemberDescriptionFactory { llvm_type: variant_llvm_type, type_metadata: variant_type_metadata, offset: FixedMemberOffset { bytes: 0 }, + flags: FLAGS_NONE } ] }, @@ -2209,6 +2220,11 @@ impl VariantMemberDescriptionFactory { _ => type_metadata(cx, ty, self.span) }, offset: ComputedMemberOffset, + flags: if self.discriminant_type_metadata.is_some() && i == 0 { + FLAGS_ARTIFICAL + } else { + FLAGS_NONE + } } }).collect() } @@ -2524,7 +2540,7 @@ fn set_members_of_composite_type(cx: &CrateContext, bytes_to_bits(member_size), bytes_to_bits(member_align), bytes_to_bits(member_offset), - 0, + member_description.flags, member_description.type_metadata) } }) @@ -2611,30 +2627,35 @@ fn at_box_metadata(cx: &CrateContext, llvm_type: *member_llvm_types.get(0), type_metadata: type_metadata(cx, int_type, codemap::DUMMY_SP), offset: ComputedMemberOffset, + flags: FLAGS_ARTIFICAL, }, MemberDescription { name: "drop_glue".to_string(), llvm_type: *member_llvm_types.get(1), type_metadata: nil_pointer_type_metadata, offset: ComputedMemberOffset, + flags: FLAGS_ARTIFICAL, }, MemberDescription { name: "prev".to_string(), llvm_type: *member_llvm_types.get(2), type_metadata: nil_pointer_type_metadata, offset: ComputedMemberOffset, + flags: FLAGS_ARTIFICAL, }, MemberDescription { name: "next".to_string(), llvm_type: *member_llvm_types.get(3), type_metadata: nil_pointer_type_metadata, offset: ComputedMemberOffset, + flags: FLAGS_ARTIFICAL, }, MemberDescription { name: "val".to_string(), llvm_type: *member_llvm_types.get(4), type_metadata: content_type_metadata, offset: ComputedMemberOffset, + flags: FLAGS_ARTIFICAL, } ]; @@ -2735,12 +2756,14 @@ fn vec_slice_metadata(cx: &CrateContext, llvm_type: *member_llvm_types.get(0), type_metadata: element_type_metadata, offset: ComputedMemberOffset, + flags: FLAGS_ARTIFICAL }, MemberDescription { name: "length".to_string(), llvm_type: *member_llvm_types.get(1), type_metadata: type_metadata(cx, ty::mk_uint(), span), offset: ComputedMemberOffset, + flags: FLAGS_ARTIFICAL }, ]; diff --git a/src/librustc_llvm/lib.rs b/src/librustc_llvm/lib.rs index 1ac0aee85d4..897477485a2 100644 --- a/src/librustc_llvm/lib.rs +++ b/src/librustc_llvm/lib.rs @@ -428,7 +428,10 @@ pub mod debuginfo { FlagObjcClassComplete = 1 << 9, FlagObjectPointer = 1 << 10, FlagVector = 1 << 11, - FlagStaticMember = 1 << 12 + FlagStaticMember = 1 << 12, + FlagIndirectVariable = 1 << 13, + FlagLValueReference = 1 << 14, + FlagRValueReference = 1 << 15 } } diff --git a/src/test/debuginfo/gdb-pretty-struct-and-enums.rs b/src/test/debuginfo/gdb-pretty-struct-and-enums.rs new file mode 100644 index 00000000000..51dad709b6f --- /dev/null +++ b/src/test/debuginfo/gdb-pretty-struct-and-enums.rs @@ -0,0 +1,167 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-tidy-linelength +// ignore-lldb +// ignore-android: FIXME(#10381) +// compile-flags:-g +// gdb-use-pretty-printer + +// The following line actually doesn't have to do anything with pretty printing, +// it just tells GDB to print values on one line: +// gdb-command: set print pretty off + +// gdb-command: rbreak zzz +// gdb-command: run +// gdb-command: finish + +// gdb-command: print regular_struct +// gdb-check:$1 = RegularStruct = {the_first_field = 101, the_second_field = 102.5, the_third_field = false, the_fourth_field = "I'm so pretty, oh so pretty..."} + +// gdb-command: print tuple +// gdb-check:$2 = {true, 103, "blub"} + +// gdb-command: print tuple_struct +// gdb-check:$3 = TupleStruct = {-104.5, 105} + +// gdb-command: print empty_struct +// gdb-check:$4 = EmptyStruct + +// gdb-command: print c_style_enum1 +// gdb-check:$5 = CStyleEnumVar1 + +// gdb-command: print c_style_enum2 +// gdb-check:$6 = CStyleEnumVar2 + +// gdb-command: print c_style_enum3 +// gdb-check:$7 = CStyleEnumVar3 + +// gdb-command: print mixed_enum_c_style_var +// gdb-check:$8 = MixedEnumCStyleVar + +// gdb-command: print mixed_enum_tuple_var +// gdb-check:$9 = MixedEnumTupleVar = {106, 107, false} + +// gdb-command: print mixed_enum_struct_var +// gdb-check:$10 = MixedEnumStructVar = {field1 = 108.5, field2 = 109} + +// gdb-command: print some +// gdb-check:$11 = Some = {110} + +// gdb-command: print none +// gdb-check:$12 = None + +// gdb-command: print nested_variant1 +// gdb-check:$13 = NestedVariant1 = {NestedStruct = {regular_struct = RegularStruct = {the_first_field = 111, the_second_field = 112.5, the_third_field = true, the_fourth_field = "NestedStructString1"}, tuple_struct = TupleStruct = {113.5, 114}, empty_struct = EmptyStruct, c_style_enum = CStyleEnumVar2, mixed_enum = MixedEnumTupleVar = {115, 116, false}}} + +// gdb-command: print nested_variant2 +// gdb-check:$14 = NestedVariant2 = {abc = NestedStruct = {regular_struct = RegularStruct = {the_first_field = 117, the_second_field = 118.5, the_third_field = false, the_fourth_field = "NestedStructString10"}, tuple_struct = TupleStruct = {119.5, 120}, empty_struct = EmptyStruct, c_style_enum = CStyleEnumVar3, mixed_enum = MixedEnumStructVar = {field1 = 121.5, field2 = -122}}} + +#![feature(struct_variant)] + +struct RegularStruct { + the_first_field: int, + the_second_field: f64, + the_third_field: bool, + the_fourth_field: &'static str, +} + +struct TupleStruct(f64, i16); + +struct EmptyStruct; + +enum CStyleEnum { + CStyleEnumVar1, + CStyleEnumVar2, + CStyleEnumVar3, +} + +enum MixedEnum { + MixedEnumCStyleVar, + MixedEnumTupleVar(u32, u16, bool), + MixedEnumStructVar { field1: f64, field2: i32 } +} + +struct NestedStruct { + regular_struct: RegularStruct, + tuple_struct: TupleStruct, + empty_struct: EmptyStruct, + c_style_enum: CStyleEnum, + mixed_enum: MixedEnum, +} + +enum NestedEnum { + NestedVariant1(NestedStruct), + NestedVariant2 { abc: NestedStruct } +} + +fn main() { + + let regular_struct = RegularStruct { + the_first_field: 101, + the_second_field: 102.5, + the_third_field: false, + the_fourth_field: "I'm so pretty, oh so pretty..." + }; + + let tuple = ( true, 103u32, "blub" ); + + let tuple_struct = TupleStruct(-104.5, 105); + + let empty_struct = EmptyStruct; + + let c_style_enum1 = CStyleEnumVar1; + let c_style_enum2 = CStyleEnumVar2; + let c_style_enum3 = CStyleEnumVar3; + + let mixed_enum_c_style_var = MixedEnumCStyleVar; + let mixed_enum_tuple_var = MixedEnumTupleVar(106, 107, false); + let mixed_enum_struct_var = MixedEnumStructVar { field1: 108.5, field2: 109 }; + + let some = Some(110u); + let none: Option = None; + + let nested_variant1 = NestedVariant1( + NestedStruct { + regular_struct: RegularStruct { + the_first_field: 111, + the_second_field: 112.5, + the_third_field: true, + the_fourth_field: "NestedStructString1", + }, + tuple_struct: TupleStruct(113.5, 114), + empty_struct: EmptyStruct, + c_style_enum: CStyleEnumVar2, + mixed_enum: MixedEnumTupleVar(115, 116, false) + } + ); + + let nested_variant2 = NestedVariant2 { + abc: NestedStruct { + regular_struct: RegularStruct { + the_first_field: 117, + the_second_field: 118.5, + the_third_field: false, + the_fourth_field: "NestedStructString10", + }, + tuple_struct: TupleStruct(119.5, 120), + empty_struct: EmptyStruct, + c_style_enum: CStyleEnumVar3, + mixed_enum: MixedEnumStructVar { + field1: 121.5, + field2: -122 + } + } + }; + + zzz(); +} + +fn zzz() { () } \ No newline at end of file From 849ae5d8815c54d135eb9d58cae77c2e6dcac55a Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Wed, 20 Aug 2014 12:53:50 +0200 Subject: [PATCH 3/4] debuginfo: Emit different autotest debugger scripts depending on GDB version. --- configure | 7 ++ mk/tests.mk | 1 + src/compiletest/common.rs | 3 + src/compiletest/compiletest.rs | 19 +++++ src/compiletest/header.rs | 69 +++++++++++++---- src/compiletest/runtest.rs | 48 ++++++++---- src/etc/gdb_rust_pretty_printing.py | 16 ++-- ...gdb-pretty-struct-and-enums-pre-gdb-7-7.rs | 75 +++++++++++++++++++ .../debuginfo/gdb-pretty-struct-and-enums.rs | 7 +- 9 files changed, 210 insertions(+), 35 deletions(-) create mode 100644 src/test/debuginfo/gdb-pretty-struct-and-enums-pre-gdb-7-7.rs diff --git a/configure b/configure index 5a2f9b5f20c..1d6c387caa7 100755 --- a/configure +++ b/configure @@ -515,6 +515,13 @@ probe CFG_LUALATEX lualatex probe CFG_GDB gdb probe CFG_LLDB lldb +if [ ! -z "$CFG_GDB" ] +then + # Extract the version + CFG_GDB_VERSION=$($CFG_GDB --version 2>/dev/null | head -1) + putvar CFG_GDB_VERSION +fi + if [ ! -z "$CFG_LLDB" ] then # If CFG_LLDB_PYTHON_DIR is not already set from the outside and valid, try to read it from diff --git a/mk/tests.mk b/mk/tests.mk index f7824304680..d95f886e078 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -623,6 +623,7 @@ CTEST_COMMON_ARGS$(1)-T-$(2)-H-$(3) := \ --stage-id stage$(1)-$(2) \ --target $(2) \ --host $(3) \ + --gdb-version="$(CFG_GDB_VERSION)" \ --android-cross-path=$(CFG_ANDROID_CROSS_PATH) \ --adb-path=$(CFG_ADB) \ --adb-test-dir=$(CFG_ADB_TEST_DIR) \ diff --git a/src/compiletest/common.rs b/src/compiletest/common.rs index ba201a4a633..afe2d071461 100644 --- a/src/compiletest/common.rs +++ b/src/compiletest/common.rs @@ -130,6 +130,9 @@ pub struct Config { // Host triple for the compiler being invoked pub host: String, + // Version of GDB + pub gdb_version: Option, + // Path to the android tools pub android_cross_path: Path, diff --git a/src/compiletest/compiletest.rs b/src/compiletest/compiletest.rs index 583d9249b35..31b37070d2f 100644 --- a/src/compiletest/compiletest.rs +++ b/src/compiletest/compiletest.rs @@ -81,6 +81,7 @@ pub fn parse_config(args: Vec ) -> Config { optflag("", "jit", "run tests under the JIT"), optopt("", "target", "the target to build for", "TARGET"), optopt("", "host", "the host to build for", "HOST"), + optopt("", "gdb-version", "the version of GDB used", "MAJOR.MINOR"), optopt("", "android-cross-path", "Android NDK standalone path", "PATH"), optopt("", "adb-path", "path to the android debugger", "PATH"), optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH"), @@ -157,6 +158,7 @@ pub fn parse_config(args: Vec ) -> Config { jit: matches.opt_present("jit"), target: opt_str2(matches.opt_str("target")), host: opt_str2(matches.opt_str("host")), + gdb_version: extract_gdb_version(matches.opt_str("gdb-version")), android_cross_path: opt_path(matches, "android-cross-path"), adb_path: opt_str2(matches.opt_str("adb-path")), adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")), @@ -376,3 +378,20 @@ pub fn make_metrics_test_closure(config: &Config, testfile: &Path) -> test::Test runtest::run_metrics(config, testfile, mm) }) } + +fn extract_gdb_version(full_version_line: Option) -> Option { + match full_version_line { + Some(full_version_line) => { + let full_version_line = full_version_line.as_slice().trim(); + let re = Regex::new(r"[^0-9]([0-9]\.[0-9])([^0-9]|$)").unwrap(); + + match re.captures(full_version_line) { + Some(captures) => { + Some(captures.at(1).to_string()) + } + None => None + } + }, + None => None + } +} \ No newline at end of file diff --git a/src/compiletest/header.rs b/src/compiletest/header.rs index f6cd217b580..9ad2582dec8 100644 --- a/src/compiletest/header.rs +++ b/src/compiletest/header.rs @@ -12,6 +12,8 @@ use common::Config; use common; use util; +use std::from_str::FromStr; + pub struct TestProps { // Lines that should be expected, in order, on standard out pub error_patterns: Vec , @@ -142,23 +144,42 @@ pub fn is_test_ignored(config: &Config, testfile: &Path) -> bool { format!("ignore-{}", config.stage_id.as_slice().split('-').next().unwrap()) } + fn ignore_gdb(config: &Config, line: &str) -> bool { + if config.mode != common::DebugInfoGdb { + return false; + } + + if parse_name_directive(line, "ignore-gdb") { + return true; + } + + match config.gdb_version { + Some(ref actual_version) => { + if line.contains("min-gdb-version") { + let min_version = line.trim() + .split(' ') + .last() + .expect("Malformed GDB version directive"); + // Ignore if actual version is smaller the minimum required + // version + gdb_version_to_int(actual_version.as_slice()) < + gdb_version_to_int(min_version.as_slice()) + } else { + false + } + } + None => false + } + } let val = iter_header(testfile, |ln| { - if parse_name_directive(ln, "ignore-test") { - false - } else if parse_name_directive(ln, ignore_target(config).as_slice()) { - false - } else if parse_name_directive(ln, ignore_stage(config).as_slice()) { - false - } else if config.mode == common::Pretty && - parse_name_directive(ln, "ignore-pretty") { - false - } else if config.target != config.host && - parse_name_directive(ln, "ignore-cross-compile") { - false - } else { - true - } + !parse_name_directive(ln, "ignore-test") && + !parse_name_directive(ln, ignore_target(config).as_slice()) && + !parse_name_directive(ln, ignore_stage(config).as_slice()) && + !(config.mode == common::Pretty && parse_name_directive(ln, "ignore-pretty")) && + !(config.target != config.host && parse_name_directive(ln, "ignore-cross-compile")) && + !ignore_gdb(config, ln) && + !(config.mode == common::DebugInfoLldb && parse_name_directive(ln, "ignore-lldb")) }); !val @@ -278,3 +299,21 @@ pub fn parse_name_value_directive(line: &str, directive: &str) None => None } } + +pub fn gdb_version_to_int(version_string: &str) -> int { + let error_string = format!( + "Encountered GDB version string with unexpected format: {}", + version_string); + let error_string = error_string.as_slice(); + + let components: Vec<&str> = version_string.trim().split('.').collect(); + + if components.len() != 2 { + fail!("{}", error_string); + } + + let major: int = FromStr::from_str(components[0]).expect(error_string); + let minor: int = FromStr::from_str(components[1]).expect(error_string); + + return major * 1000 + minor; +} diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs index 27f6fbcf9f6..6a1e1c6cc76 100644 --- a/src/compiletest/runtest.rs +++ b/src/compiletest/runtest.rs @@ -466,11 +466,39 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { .unwrap() .to_string(); // write debugger script - let script_str = [ - "set charset UTF-8".to_string(), - cmds, - "quit\n".to_string() - ].connect("\n"); + let mut script_str = String::with_capacity(2048); + + script_str.push_str("set charset UTF-8\n"); + script_str.push_str("show version\n"); + + match config.gdb_version { + Some(ref version) => { + if header::gdb_version_to_int(version.as_slice()) > + header::gdb_version_to_int("7.4") { + // Add the directory containing the pretty printers to + // GDB's script auto loading safe path ... + script_str.push_str( + format!("add-auto-load-safe-path {}\n", + rust_pp_module_abs_path.as_slice()) + .as_slice()); + // ... and also the test directory + script_str.push_str( + format!("add-auto-load-safe-path {}\n", + config.build_base.as_str().unwrap()) + .as_slice()); + } + } + _ => { /* nothing to do */ } + } + + // Load the target executable + script_str.push_str(format!("file {}\n", + exe_file.as_str().unwrap()) + .as_slice()); + + script_str.push_str(cmds.as_slice()); + script_str.push_str("quit\n"); + debug!("script_str = {}", script_str); dump_output_file(config, testfile, @@ -500,15 +528,7 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { vec!("-quiet".to_string(), "-batch".to_string(), "-nx".to_string(), - // Add the directory containing the pretty printers to - // GDB's script auto loading safe path ... - format!("-iex=add-auto-load-safe-path {}", - rust_pp_module_abs_path.as_slice()), - // ... and also the test directory - format!("-iex=add-auto-load-safe-path {}", - config.build_base.as_str().unwrap()), - format!("-command={}", debugger_script.as_str().unwrap()), - exe_file.as_str().unwrap().to_string()); + format!("-command={}", debugger_script.as_str().unwrap())); let proc_args = ProcArgs { prog: debugger(), diff --git a/src/etc/gdb_rust_pretty_printing.py b/src/etc/gdb_rust_pretty_printing.py index c84dde92f8f..e8a6427c1d7 100644 --- a/src/etc/gdb_rust_pretty_printing.py +++ b/src/etc/gdb_rust_pretty_printing.py @@ -80,8 +80,7 @@ def rust_pretty_printer_lookup_function(val): discriminant_name, discriminant_val = extract_discriminant_value(val) return rust_pretty_printer_lookup_function(val[enum_members[discriminant_val]]) - - + # No pretty printer has been found return None #=------------------------------------------------------------------------------ @@ -99,10 +98,17 @@ class RustStructPrinter: def children(self): cs = [] for field in self.val.type.fields(): - field_name = field.name; + field_name = field.name + # Normally the field name is used as a key to access the field value, + # because that's also supported in older versions of GDB... + field_key = field_name if field_name == None: field_name = "" - name_value_tuple = ( field_name, self.val[field] ) + # ... but for fields without a name (as in tuples), we have to fall back + # to the newer method of using the field object directly as key. In + # older versions of GDB, this will just fail. + field_key = field + name_value_tuple = ( field_name, self.val[field_key] ) cs.append( name_value_tuple ) if self.hide_first_field: @@ -222,4 +228,4 @@ def get_field_at_index(val, index): for field in val.type.fields(): if i == index: return field - return None \ No newline at end of file + return None diff --git a/src/test/debuginfo/gdb-pretty-struct-and-enums-pre-gdb-7-7.rs b/src/test/debuginfo/gdb-pretty-struct-and-enums-pre-gdb-7-7.rs new file mode 100644 index 00000000000..e9daf31be2c --- /dev/null +++ b/src/test/debuginfo/gdb-pretty-struct-and-enums-pre-gdb-7-7.rs @@ -0,0 +1,75 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// This test uses only GDB Python API features which should be available in +// older versions of GDB too. A more extensive test can be found in +// gdb-pretty-struct-and-enums.rs + +// ignore-tidy-linelength +// ignore-lldb +// ignore-android: FIXME(#10381) +// compile-flags:-g +// gdb-use-pretty-printer + +// The following line actually doesn't have to do anything with pretty printing, +// it just tells GDB to print values on one line: +// gdb-command: set print pretty off + +// gdb-command: rbreak zzz +// gdb-command: run +// gdb-command: finish + +// gdb-command: print regular_struct +// gdb-check:$1 = RegularStruct = {the_first_field = 101, the_second_field = 102.5, the_third_field = false} + +// gdb-command: print empty_struct +// gdb-check:$2 = EmptyStruct + +// gdb-command: print c_style_enum1 +// gdb-check:$3 = CStyleEnumVar1 + +// gdb-command: print c_style_enum2 +// gdb-check:$4 = CStyleEnumVar2 + +// gdb-command: print c_style_enum3 +// gdb-check:$5 = CStyleEnumVar3 + +struct RegularStruct { + the_first_field: int, + the_second_field: f64, + the_third_field: bool, +} + +struct EmptyStruct; + +enum CStyleEnum { + CStyleEnumVar1, + CStyleEnumVar2, + CStyleEnumVar3, +} + +fn main() { + + let regular_struct = RegularStruct { + the_first_field: 101, + the_second_field: 102.5, + the_third_field: false + }; + + let empty_struct = EmptyStruct; + + let c_style_enum1 = CStyleEnumVar1; + let c_style_enum2 = CStyleEnumVar2; + let c_style_enum3 = CStyleEnumVar3; + + zzz(); +} + +fn zzz() { () } diff --git a/src/test/debuginfo/gdb-pretty-struct-and-enums.rs b/src/test/debuginfo/gdb-pretty-struct-and-enums.rs index 51dad709b6f..5ef63da71af 100644 --- a/src/test/debuginfo/gdb-pretty-struct-and-enums.rs +++ b/src/test/debuginfo/gdb-pretty-struct-and-enums.rs @@ -14,6 +14,11 @@ // compile-flags:-g // gdb-use-pretty-printer +// This test uses some GDB Python API features (e.g. accessing anonymous fields) +// which are only available in newer GDB version. The following directive will +// case the test runner to ignore this test if an older GDB version is used: +// min-gdb-version 7.7 + // The following line actually doesn't have to do anything with pretty printing, // it just tells GDB to print values on one line: // gdb-command: set print pretty off @@ -164,4 +169,4 @@ fn main() { zzz(); } -fn zzz() { () } \ No newline at end of file +fn zzz() { () } From e72e4dfc74ca65d6b3e0d821b94a34764ab2c63d Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Wed, 27 Aug 2014 15:18:16 +0200 Subject: [PATCH 4/4] debuginfo: Improve GDB version handling in compiletest tool --- src/compiletest/compiletest.rs | 16 +++++++++++----- src/compiletest/runtest.rs | 8 +++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/compiletest/compiletest.rs b/src/compiletest/compiletest.rs index 31b37070d2f..6c6cd6f610f 100644 --- a/src/compiletest/compiletest.rs +++ b/src/compiletest/compiletest.rs @@ -381,17 +381,23 @@ pub fn make_metrics_test_closure(config: &Config, testfile: &Path) -> test::Test fn extract_gdb_version(full_version_line: Option) -> Option { match full_version_line { - Some(full_version_line) => { + Some(ref full_version_line) + if full_version_line.as_slice().trim().len() > 0 => { let full_version_line = full_version_line.as_slice().trim(); - let re = Regex::new(r"[^0-9]([0-9]\.[0-9])([^0-9]|$)").unwrap(); + + let re = Regex::new(r"(^|[^0-9])([0-9]\.[0-9])([^0-9]|$)").unwrap(); match re.captures(full_version_line) { Some(captures) => { - Some(captures.at(1).to_string()) + Some(captures.at(2).to_string()) + } + None => { + println!("Could not extract GDB version from line '{}'", + full_version_line); + None } - None => None } }, - None => None + _ => None } } \ No newline at end of file diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs index 6a1e1c6cc76..34f46a5f0d6 100644 --- a/src/compiletest/runtest.rs +++ b/src/compiletest/runtest.rs @@ -473,6 +473,9 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { match config.gdb_version { Some(ref version) => { + println!("NOTE: compiletest thinks it is using GDB version {}", + version.as_slice()); + if header::gdb_version_to_int(version.as_slice()) > header::gdb_version_to_int("7.4") { // Add the directory containing the pretty printers to @@ -488,7 +491,10 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { .as_slice()); } } - _ => { /* nothing to do */ } + _ => { + println!("NOTE: compiletest does not know which version of \ + GDB it is using"); + } } // Load the target executable