4

I am absolute beginner to Shell scripting. My task is to make a script, which will show functions used in a file (caller and callee). I have used objdump, grep, awk etc. to get this output:

000000000040090d <usage>: 000000000040095d <failure>: 400970: e8 98 ff ff ff callq 40090d <usage> 000000000040097f <strton>: 4009bc: e8 9c ff ff ff callq 40095d <failure> 00000000004009c6 <main>: 400a0e: e8 6c ff ff ff callq 40097f <strton> 400a26: e8 32 ff ff ff callq 40095d <failure> 400a41: e8 39 ff ff ff callq 40097f <strton> 400a59: e8 ff fe ff ff callq 40095d <failure> 400a9a: e8 be fe ff ff callq 40095d <failure> 400aae: e8 cc fe ff ff callq 40097f <strton> 400ac2: e8 b8 fe ff ff callq 40097f <strton> 400ad1: e8 87 fe ff ff callq 40095d <failure> 400afe: e8 fe 01 00 00 callq 400d01 <set_timeout> 400b1c: e8 3c fe ff ff callq 40095d <failure> 400b26: e8 19 00 00 00 callq 400b44 <print_fib_upto> 400b37: e8 89 00 00 00 callq 400bc5 <print_ackermann> 

Okay, the result should look like this:

failure -> usage strton -> failure main -> failure main -> print_ackermann main -> print_fib_upto main -> set_timeout main -> strton 

But I have no idea how to accomplish it. I'd know how to do it in C, etc, but not here. I think this is the right pseudo-code.

If (end of line == ">:") caller == last column; while (end of line == ">") { callee == last column; echo "$caller -> $callee" } 

Can anyone tell me, how to write this in BASH? Thank you very much, Maybe it's a silly question, but I don't know anyting about shell yet.

3
  • Curious. You've been directed to write this in pure BASH? If not, then why not use C? If so, it's unusual to be forbidden to use awk and sed, which are both written in C. Commented Mar 19, 2014 at 21:00
  • I do IT school, this is our project, that's why. We have shell subject. And we can use awk and sed. Commented Mar 19, 2014 at 21:01
  • I think you can do everything you want by piping the output of objdump into a short sed script, obviating the need for grep, awk, etc. See below. Works for me. Commented Mar 19, 2014 at 21:29

6 Answers 6

4

For example:

#!/bin/bash while read -r line do case "$line" in *:) caller=$line; continue;; *) echo "$caller $line";; esac done < <(sed 's/.*</</' < caldata) | sed 's/[<>]//g;s/:/ -> /' | sort -u 

produces:

failure -> usage main -> failure main -> print_ackermann main -> print_fib_upto main -> set_timeout main -> strton strton -> failure 
Sign up to request clarification or add additional context in comments.

Comments

2

You can use awk:

awk -F'[<>:]+' 'NF==3{p=$(NF-1); next} {print p, "->", $(NF-1)}' file failure -> usage strton -> failure main -> strton main -> failure main -> strton main -> failure main -> failure main -> strton main -> strton main -> failure main -> set_timeout main -> failure main -> print_fib_upto main -> print_ackermann 

1 Comment

Wow, looks quite complicated to me, but makes sense, thank you!
2

I think this might be easier with awk, but I interpreted your pseudocode directly into bash.

while read; do # If not given an argument `read` puts the line into `REPLY` case $REPLY in # Use a case expression for pattern matching. *>:) caller="${REPLY##*<}" caller="${caller%>:}" # Parameter expansions to strip off unwanted parts of the line. ;; *>) callee="${REPLY##*<}" callee="${callee%>}" ;; esac echo "$caller -> $callee" done 

Comments

2

You can try this awk:

$ awk 'NF==2 { gsub(/[[:punct:]]/, "", $NF) caller = $NF next } { gsub(/[[:punct:]]/, "", $NF) map[caller,$NF]++ } END { for(calls in map) { n = split(calls, tmp, SUBSEP) print tmp[1]" -> "tmp[2] } }' file main -> printackermann main -> settimeout strton -> failure main -> printfibupto main -> failure failure -> usage main -> strton 

or

$ awk ' NF==2 { for(keys in map) print map[keys]" -> "keys; delete map gsub(/[[:punct:]]/, "", $NF) caller = $NF next } { gsub(/[[:punct:]]/, "", $NF) map[$NF] = caller } END { for(keys in map) print map[keys]" -> "keys }' file failure -> usage strton -> failure main -> strton main -> settimeout main -> printackermann main -> printfibupto main -> failure 

Comments

2

The whole thing can be done with sed reasonably easily, negating the need for preprocessing with grep, etc. The following isn't perfect (you may have to tweak regexes and what is/isn't considered a match), but it should break the back of it.

#!/bin/bash symbol='[_A-Za-z][_@A-Za-z0-9]\+' objdump -d "$1" | sed -n " /^[0-9a-f]\+ <\(${symbol}\)>:/{ # Match symbol in header s//\1/ # Symbol only in pattern space h # Save symbol to hold space : loop # (until empty line) n # Next line /^$/!{ # If not an empty line # Try matching 'callq' line and extract symbol: /^[:[:space:][:xdigit:]]\+callq[^<]\+<\($symbol\)>/{ s//\1/ # Symbol only in pattern space G # Append hold space to pattern space # Swap words in pattern space, adding separator: s/\($symbol\)[[:space:]]\($symbol\)/\2 -> \1/g p # Print pattern space: } b loop # Try next line } }" 

2 Comments

As a dance of an crazy toothpicks. :) Love it, +1
@jm666: It looks worse than it is, but I admit it'd be hairy if it was all on one line with no comments :)
1

Already answered, but for pure bash, it could be done as follows:

#!/bin/bash while read line do [[ "$line" =~ \<(.*)\>\:$ ]] && caller=${BASH_REMATCH[1]} [[ "$line" =~ \<(.*)\>$ ]] && callee=${BASH_REMATCH[1]} [ -n $caller -a -n $callee ] && echo "$caller -> $callee"; callee="" done < $1 

The [[ expr ]] && means test expr, and if true, run what's afterwards. The =~ is a regexp matcher, where the value in the ()'s is stored in ${BASH_REMATCH[1]}.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.