开发者

Bash - how to count number of instructions?

开发者 https://www.devze.com 2023-01-27 05:45 出处:网络
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:

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

0

精彩评论

暂无评论...
验证码 换一张
取 消