简体   繁体   中英

Bash - how to count number of instructions?

I have a number of *.s assembly files. I would like to count how many instructions they contain. For example, to count all mul instructions I do:

cat *.s | grep -P '\t'"mul" | wc -l

which gives me a compound number of all mul instructions.

I would like to have an output like:

mul: 893
add: 12054
sub: 2356
...

The problem is that there is no table of instructions supported by a target platform. Each target platform has different set of instructions. The supported instructions must be deduced from existed assembly files. Can I do it in Bash only without Perl/Python foundry?

You can get all of them like this:

grep -hP '^[ \t]+[a-z]*' *.s | sed 's/^[ \t]\+\([a-z]*\).*/\1/' | sort | uniq -c

Edit: I changed this to work with the example provided by SiegeX.

Output:

1 call
6 mov
1 push
1 ret
2 syscall
2 xor

Assuming your instructions are all indented from the other code by use of white space (tab is OK), then this awk 1-liner will work:

awk '/^[[:space:]]+/{a[$1]++}END{for(op in a){print op,a[op]}}' /path/to/*.s

Input

$ cat asm.s
section .text
global _start, write
write:
  mov al,1 ;write syscall
  syscall
  ret
_start:
  mov rax,0x0a68732f6e69622f
  push rax
  xor rax,rax
  mov rsi,rsp
  mov rdi,1
  mov rdx,8
  call write

exit: ; just exit not a function
  xor rax,rax
  mov rax,60
  syscall

Output

$ awk '/^[[:space:]]+/{a[$1]++}END{for(op in a){print op,a[op]}}' ./*.s
push 1
xor 2
ret 1
mov 6
syscall 2
call 1

Bash 4 without any external utilities:

declare -A c;while IFS=$'\n' read -r t;do [[ ! $t =~ ^[[:space:]] ]] && continue;w=($t);((c[${w[0]}]++));done<asm.s;for op in ${!c[@]};do echo $op ${c[$op]};done

To do multiple files, wrap the while loop in a for f in *.s loop with the while's done like this: done < "$f" or do the while's done like this: done < <(cat *.s) instead of the for f .

Slightly shorter to count the first word on each line without regard to indentation:

declare -A c;while read -ra t;do [[ $t ]]&&((c[${t[0]}]++));done<asm.s;for op in ${!c[@]};do echo $op ${c[$op]};done

Still, it's longer than the other answers.

Just for the hell of it, SiegeX's awk answer in Perl

perl -n '/^\s+(\w+)/&&$a{$1}++;END{print map{"$_ $a{$_}\n"}keys%a}' ./*.s

Because "anything you can do I can do in a manner which more closely resembles line noise" (-:

grep -Eo '\\w+' /source.file | uniq -c

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM