X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=scripts%2Frecordmcount.pl;h=f3c9c0a90b98cad68389da5d591a9921e1cb8f39;hb=91803b499cca2fe558abad709ce83dc896b80950;hp=e4922a6c963b8907189a8e0981c149a91fb669f5;hpb=3989cce82b272bd6312555fcc47c11715c157102;p=safe%2Fjmp%2Flinux-2.6 diff --git a/scripts/recordmcount.pl b/scripts/recordmcount.pl index e4922a6..f3c9c0a 100755 --- a/scripts/recordmcount.pl +++ b/scripts/recordmcount.pl @@ -6,78 +6,93 @@ # all the offsets to the calls to mcount. # # -# What we want to end up with is a section in vmlinux called -# __mcount_loc that contains a list of pointers to all the -# call sites in the kernel that call mcount. Later on boot up, the kernel -# will read this list, save the locations and turn them into nops. -# When tracing or profiling is later enabled, these locations will then -# be converted back to pointers to some function. +# What we want to end up with this is that each object file will have a +# section called __mcount_loc that will hold the list of pointers to mcount +# callers. After final linking, the vmlinux will have within .init.data the +# list of all callers to mcount between __start_mcount_loc and __stop_mcount_loc. +# Later on boot up, the kernel will read this list, save the locations and turn +# them into nops. When tracing or profiling is later enabled, these locations +# will then be converted back to pointers to some function. # # This is no easy feat. This script is called just after the original # object is compiled and before it is linked. # -# The references to the call sites are offsets from the section of text -# that the call site is in. Hence, all functions in a section that -# has a call site to mcount, will have the offset from the beginning of -# the section and not the beginning of the function. +# When parse this object file using 'objdump', the references to the call +# sites are offsets from the section that the call site is in. Hence, all +# functions in a section that has a call site to mcount, will have the +# offset from the beginning of the section and not the beginning of the +# function. +# +# But where this section will reside finally in vmlinx is undetermined at +# this point. So we can't use this kind of offsets to record the final +# address of this call site. +# +# The trick is to change the call offset referring the start of a section to +# referring a function symbol in this section. During the link step, 'ld' will +# compute the final address according to the information we record. # -# The trick is to find a way to record the beginning of the section. -# The way we do this is to look at the first function in the section -# which will also be the location of that section after final link. # e.g. # -# .section ".text.sched" -# .globl my_func -# my_func: +# .section ".sched.text", "ax" +# [...] +# func1: # [...] -# call mcount (offset: 0x5) +# call mcount (offset: 0x10) # [...] # ret -# other_func: +# .globl fun2 +# func2: (offset: 0x20) # [...] -# call mcount (offset: 0x1b) +# [...] +# ret +# func3: +# [...] +# call mcount (offset: 0x30) # [...] # # Both relocation offsets for the mcounts in the above example will be -# offset from .text.sched. If we make another file called tmp.s with: +# offset from .sched.text. If we choose global symbol func2 as a reference and +# make another file called tmp.s with the new offsets: # # .section __mcount_loc -# .quad my_func + 0x5 -# .quad my_func + 0x1b +# .quad func2 - 0x10 +# .quad func2 + 0x10 # -# We can then compile this tmp.s into tmp.o, and link it to the original +# We can then compile this tmp.s into tmp.o, and link it back to the original # object. # -# But this gets hard if my_func is not globl (a static function). -# In such a case we have: +# In our algorithm, we will choose the first global function we meet in this +# section as the reference. But this gets hard if there is no global functions +# in this section. In such a case we have to select a local one. E.g. func1: # -# .section ".text.sched" -# my_func: +# .section ".sched.text", "ax" +# func1: # [...] -# call mcount (offset: 0x5) +# call mcount (offset: 0x10) # [...] # ret -# .globl my_func -# other_func: +# func2: # [...] -# call mcount (offset: 0x1b) +# call mcount (offset: 0x20) # [...] +# .section "other.section" # # If we make the tmp.s the same as above, when we link together with -# the original object, we will end up with two symbols for my_func: +# the original object, we will end up with two symbols for func1: # one local, one global. After final compile, we will end up with -# an undefined reference to my_func. +# an undefined reference to func1 or a wrong reference to another global +# func1 in other files. # # Since local objects can reference local variables, we need to find # a way to make tmp.o reference the local objects of the original object -# file after it is linked together. To do this, we convert the my_func +# file after it is linked together. To do this, we convert func1 # into a global symbol before linking tmp.o. Then after we link tmp.o -# we will only have a single symbol for my_func that is global. -# We can convert my_func back into a local symbol and we are done. +# we will only have a single symbol for func1 that is global. +# We can convert func1 back into a local symbol and we are done. # # Here are the steps we take: # -# 1) Record all the local symbols by using 'nm' +# 1) Record all the local and weak symbols by using 'nm' # 2) Use objdump to find all the call site offsets and sections for # mcount. # 3) Compile the list into its own object. @@ -87,10 +102,8 @@ # 6) Link together this new object with the list object. # 7) Convert the local functions back to local symbols and rename # the result as the original object. -# End. # 8) Link the object with the list object. # 9) Move the result back to the original object. -# End. # use strict; @@ -100,57 +113,244 @@ $P =~ s@.*/@@g; my $V = '0.1'; -if ($#ARGV < 6) { - print "usage: $P arch objdump objcopy cc ld nm rm mv inputfile\n"; +if ($#ARGV != 11) { + print "usage: $P arch endian bits objdump objcopy cc ld nm rm mv is_module inputfile\n"; print "version: $V\n"; exit(1); } -my ($arch, $objdump, $objcopy, $cc, $ld, $nm, $rm, $mv, $inputfile) = @ARGV; - -if ($arch eq "i386") { - $ld = "ld -m elf_i386"; - $objdump = "objdump -M i386"; - $objcopy = "objcopy -O elf32-i386"; - $cc = "gcc -m32"; -} +my ($arch, $endian, $bits, $objdump, $objcopy, $cc, + $ld, $nm, $rm, $mv, $is_module, $inputfile) = @ARGV; -if ($arch eq "x86_64") { - $ld = "ld -m elf_x86_64"; - $objdump = "objdump -M x86-64"; - $objcopy = "objcopy -O elf64-x86-64"; - $cc = "gcc -m64"; +# This file refers to mcount and shouldn't be ftraced, so lets' ignore it +if ($inputfile =~ m,kernel/trace/ftrace\.o$,) { + exit(0); } -$objdump = "objdump" if ((length $objdump) == 0); -$objcopy = "objcopy" if ((length $objcopy) == 0); -$cc = "gcc" if ((length $cc) == 0); -$ld = "ld" if ((length $ld) == 0); -$nm = "nm" if ((length $nm) == 0); -$rm = "rm" if ((length $rm) == 0); -$mv = "mv" if ((length $mv) == 0); +# Acceptable sections to record. +my %text_sections = ( + ".text" => 1, + ".sched.text" => 1, + ".spinlock.text" => 1, + ".irqentry.text" => 1, + ".text.unlikely" => 1, +); + +# Note: we are nice to C-programmers here, thus we skip the '||='-idiom. +$objdump = 'objdump' if (!$objdump); +$objcopy = 'objcopy' if (!$objcopy); +$cc = 'gcc' if (!$cc); +$ld = 'ld' if (!$ld); +$nm = 'nm' if (!$nm); +$rm = 'rm' if (!$rm); +$mv = 'mv' if (!$mv); #print STDERR "running: $P '$arch' '$objdump' '$objcopy' '$cc' '$ld' " . # "'$nm' '$rm' '$mv' '$inputfile'\n"; -my %locals; -my %convert; +my %locals; # List of local (static) functions +my %weak; # List of weak functions +my %convert; # List of local functions used that needs conversion my $type; +my $local_regex; # Match a local function (return function) +my $weak_regex; # Match a weak function (return function) my $section_regex; # Find the start of a section -my $function_regex; # Find the name of a function (return func name) +my $function_regex; # Find the name of a function + # (return offset and func name) my $mcount_regex; # Find the call site to mcount (return offset) +my $alignment; # The .align value to use for $mcount_section +my $section_type; # Section header plus possible alignment command +my $can_use_local = 0; # If we can use local function references + +# Shut up recordmcount if user has older objcopy +my $quiet_recordmcount = ".tmp_quiet_recordmcount"; +my $print_warning = 1; +$print_warning = 0 if ( -f $quiet_recordmcount); + +## +# check_objcopy - whether objcopy supports --globalize-symbols +# +# --globalize-symbols came out in 2.17, we must test the version +# of objcopy, and if it is less than 2.17, then we can not +# record local functions. +sub check_objcopy +{ + open (IN, "$objcopy --version |") or die "error running $objcopy"; + while () { + if (/objcopy.*\s(\d+)\.(\d+)/) { + $can_use_local = 1 if ($1 > 2 || ($1 == 2 && $2 >= 17)); + last; + } + } + close (IN); + + if (!$can_use_local && $print_warning) { + print STDERR "WARNING: could not find objcopy version or version " . + "is less than 2.17.\n" . + "\tLocal function references are disabled.\n"; + open (QUIET, ">$quiet_recordmcount"); + printf QUIET "Disables the warning from recordmcount.pl\n"; + close QUIET; + } +} + +if ($arch =~ /(x86(_64)?)|(i386)/) { + if ($bits == 64) { + $arch = "x86_64"; + } else { + $arch = "i386"; + } +} + +# +# We base the defaults off of i386, the other archs may +# feel free to change them in the below if statements. +# +$local_regex = "^[0-9a-fA-F]+\\s+t\\s+(\\S+)"; +$weak_regex = "^[0-9a-fA-F]+\\s+([wW])\\s+(\\S+)"; +$section_regex = "Disassembly of section\\s+(\\S+):"; +$function_regex = "^([0-9a-fA-F]+)\\s+<(.*?)>:"; +$mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\smcount\$"; +$section_type = '@progbits'; +$type = ".long"; if ($arch eq "x86_64") { - $section_regex = "Disassembly of section"; - $function_regex = "<(.*?)>:"; $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\smcount([+-]0x[0-9a-zA-Z]+)?\$"; $type = ".quad"; + $alignment = 8; + + # force flags for this arch + $ld .= " -m elf_x86_64"; + $objdump .= " -M x86-64"; + $objcopy .= " -O elf64-x86-64"; + $cc .= " -m64"; + } elsif ($arch eq "i386") { - $section_regex = "Disassembly of section"; - $function_regex = "<(.*?)>:"; - $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\smcount\$"; - $type = ".long"; + $alignment = 4; + + # force flags for this arch + $ld .= " -m elf_i386"; + $objdump .= " -M i386"; + $objcopy .= " -O elf32-i386"; + $cc .= " -m32"; + +} elsif ($arch eq "s390" && $bits == 32) { + $mcount_regex = "^\\s*([0-9a-fA-F]+):\\s*R_390_32\\s+_mcount\$"; + $alignment = 4; + $ld .= " -m elf_s390"; + $cc .= " -m31"; + +} elsif ($arch eq "s390" && $bits == 64) { + $mcount_regex = "^\\s*([0-9a-fA-F]+):\\s*R_390_(PC|PLT)32DBL\\s+_mcount\\+0x2\$"; + $alignment = 8; + $type = ".quad"; + $ld .= " -m elf64_s390"; + $cc .= " -m64"; + +} elsif ($arch eq "sh") { + $alignment = 2; + + # force flags for this arch + $ld .= " -m shlelf_linux"; + $objcopy .= " -O elf32-sh-linux"; + $cc .= " -m32"; + +} elsif ($arch eq "powerpc") { + $local_regex = "^[0-9a-fA-F]+\\s+t\\s+(\\.?\\S+)"; + $function_regex = "^([0-9a-fA-F]+)\\s+<(\\.?.*?)>:"; + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s\\.?_mcount\$"; + + if ($bits == 64) { + $type = ".quad"; + } + +} elsif ($arch eq "arm") { + $alignment = 2; + $section_type = '%progbits'; + +} elsif ($arch eq "ia64") { + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s_mcount\$"; + $type = "data8"; + + if ($is_module eq "0") { + $cc .= " -mconstant-gp"; + } +} elsif ($arch eq "sparc64") { + # In the objdump output there are giblets like: + # 0000000000000000 : + # As there's some data blobs that get emitted into the + # text section before the first instructions and the first + # real symbols. We don't want to match that, so to combat + # this we use '\w' so we'll match just plain symbol names, + # and not those that also include hex offsets inside of the + # '<>' brackets. Actually the generic function_regex setting + # could safely use this too. + $function_regex = "^([0-9a-fA-F]+)\\s+<(\\w*?)>:"; + + # Sparc64 calls '_mcount' instead of plain 'mcount'. + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s_mcount\$"; + + $alignment = 8; + $type = ".xword"; + $ld .= " -m elf64_sparc"; + $cc .= " -m64"; + $objcopy .= " -O elf64-sparc"; +} elsif ($arch eq "mips") { + # To enable module support, we need to enable the -mlong-calls option + # of gcc for module, after using this option, we can not get the real + # offset of the calling to _mcount, but the offset of the lui + # instruction or the addiu one. herein, we record the address of the + # first one, and then we can replace this instruction by a branch + # instruction to jump over the profiling function to filter the + # indicated functions, or swith back to the lui instruction to trace + # them, which means dynamic tracing. + # + # c: 3c030000 lui v1,0x0 + # c: R_MIPS_HI16 _mcount + # c: R_MIPS_NONE *ABS* + # c: R_MIPS_NONE *ABS* + # 10: 64630000 daddiu v1,v1,0 + # 10: R_MIPS_LO16 _mcount + # 10: R_MIPS_NONE *ABS* + # 10: R_MIPS_NONE *ABS* + # 14: 03e0082d move at,ra + # 18: 0060f809 jalr v1 + # + # for the kernel: + # + # 10: 03e0082d move at,ra + # 14: 0c000000 jal 0 + # 14: R_MIPS_26 _mcount + # 14: R_MIPS_NONE *ABS* + # 14: R_MIPS_NONE *ABS* + # 18: 00020021 nop + if ($is_module eq "0") { + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s_mcount\$"; + } else { + $mcount_regex = "^\\s*([0-9a-fA-F]+): R_MIPS_HI16\\s+_mcount\$"; + } + $objdump .= " -Melf-trad".$endian."mips "; + + if ($endian eq "big") { + $endian = " -EB "; + $ld .= " -melf".$bits."btsmip"; + } else { + $endian = " -EL "; + $ld .= " -melf".$bits."ltsmip"; + } + + $cc .= " -mno-abicalls -fno-pic -mabi=" . $bits . $endian; + $ld .= $endian; + + if ($bits == 64) { + $function_regex = + "^([0-9a-fA-F]+)\\s+<(.|[^\$]L.*?|\$[^L].*?|[^\$][^L].*?)>:"; + $type = ".dword"; + } +} elsif ($arch eq "microblaze") { + # Microblaze calls '_mcount' instead of plain 'mcount'. + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s_mcount\$"; } else { die "Arch $arch is not supported with CONFIG_FTRACE_MCOUNT_RECORD"; } @@ -158,7 +358,6 @@ if ($arch eq "x86_64") { my $text_found = 0; my $read_function = 0; my $opened = 0; -my $text = ""; my $mcount_section = "__mcount_loc"; my $dirname; @@ -185,47 +384,140 @@ if ($filename =~ m,^(.*)(\.\S),) { my $mcount_s = $dirname . "/.tmp_mc_" . $prefix . ".s"; my $mcount_o = $dirname . "/.tmp_mc_" . $prefix . ".o"; +check_objcopy(); + # -# Step 1: find all the local symbols (static functions). +# Step 1: find all the local (static functions) and weak symbols. +# 't' is local, 'w/W' is weak # open (IN, "$nm $inputfile|") || die "error running $nm"; while () { - if (/^[0-9a-fA-F]+\s+t\s+(\S+)/) { + if (/$local_regex/) { $locals{$1} = 1; + } elsif (/$weak_regex/) { + $weak{$2} = $1; } } close(IN); +my @offsets; # Array of offsets of mcount callers +my $ref_func; # reference function to use for offsets +my $offset = 0; # offset of ref_func to section beginning + +## +# update_funcs - print out the current mcount callers +# +# Go through the list of offsets to callers and write them to +# the output file in a format that can be read by an assembler. +# +sub update_funcs +{ + return unless ($ref_func and @offsets); + + # Sanity check on weak function. A weak function may be overwritten by + # another function of the same name, making all these offsets incorrect. + if (defined $weak{$ref_func}) { + die "$inputfile: ERROR: referencing weak function" . + " $ref_func for mcount\n"; + } + + # is this function static? If so, note this fact. + if (defined $locals{$ref_func}) { + + # only use locals if objcopy supports globalize-symbols + if (!$can_use_local) { + return; + } + $convert{$ref_func} = 1; + } + + # Loop through all the mcount caller offsets and print a reference + # to the caller based from the ref_func. + if (!$opened) { + open(FILE, ">$mcount_s") || die "can't create $mcount_s\n"; + $opened = 1; + print FILE "\t.section $mcount_section,\"a\",$section_type\n"; + print FILE "\t.align $alignment\n" if (defined($alignment)); + } + foreach my $cur_offset (@offsets) { + printf FILE "\t%s %s + %d\n", $type, $ref_func, $cur_offset - $offset; + } +} + # # Step 2: find the sections and mcount call sites # -open(IN, "$objdump -dr $inputfile|") || die "error running $objdump"; +open(IN, "$objdump -hdr $inputfile|") || die "error running $objdump"; + +my $text; + + +# read headers first +my $read_headers = 1; while () { + + if ($read_headers && /$mcount_section/) { + # + # Somehow the make process can execute this script on an + # object twice. If it does, we would duplicate the mcount + # section and it will cause the function tracer self test + # to fail. Check if the mcount section exists, and if it does, + # warn and exit. + # + print STDERR "ERROR: $mcount_section already in $inputfile\n" . + "\tThis may be an indication that your build is corrupted.\n" . + "\tDelete $inputfile and try again. If the same object file\n" . + "\tstill causes an issue, then disable CONFIG_DYNAMIC_FTRACE.\n"; + exit(-1); + } + # is it a section? if (/$section_regex/) { - $read_function = 1; + $read_headers = 0; + + # Only record text sections that we know are safe + $read_function = defined($text_sections{$1}); + # print out any recorded offsets + update_funcs(); + + # reset all markers and arrays $text_found = 0; + undef($ref_func); + undef(@offsets); + # section found, now is this a start of a function? } elsif ($read_function && /$function_regex/) { - $read_function = 0; $text_found = 1; - $text = $1; - # is this function static? If so, note this fact. - if (defined $locals{$text}) { - $convert{$text} = 1; - } - # is this a call site to mcount? If so, print the offset from the section - } elsif ($text_found && /$mcount_regex/) { - if (!$opened) { - open(FILE, ">$mcount_s") || die "can't create $mcount_s\n"; - $opened = 1; - print FILE "\t.section $mcount_section,\"a\",\@progbits\n"; + $text = $2; + + # if this is either a local function or a weak function + # keep looking for functions that are global that + # we can use safely. + if (!defined($locals{$text}) && !defined($weak{$text})) { + $ref_func = $text; + $read_function = 0; + $offset = hex $1; + } else { + # if we already have a function, and this is weak, skip it + if (!defined($ref_func) && !defined($weak{$text}) && + # PPC64 can have symbols that start with .L and + # gcc considers these special. Don't use them! + $text !~ /^\.L/) { + $ref_func = $text; + $offset = hex $1; + } } - print FILE "\t$type $text + 0x$1\n"; + } + # is this a call site to mcount? If so, record it to print later + if ($text_found && /$mcount_regex/) { + push(@offsets, hex $1); } } +# dump out anymore offsets that may have been found +update_funcs(); + # If we did not find any mcount callers, we are done (do nothing). if (!$opened) { exit(0);