193 lines
5.9 KiB
Ruby
193 lines
5.9 KiB
Ruby
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
#
|
|
# Licence is LGPL, see LICENCE in the top-level directory
|
|
#
|
|
# parses the PPC specification PDF to generate the opcode list
|
|
#
|
|
|
|
require 'pdfparse'
|
|
|
|
$field_mask = {}
|
|
$field_shift = {}
|
|
$opcodes = []
|
|
def make_instr(bins, bits, text)
|
|
# calc bitfields length from their offset
|
|
last = 32
|
|
bitlen = []
|
|
bits.reverse_each { |bit|
|
|
bitlen.unshift last-bit
|
|
last = bit
|
|
}
|
|
|
|
# the opcode binary value (w/o fields)
|
|
bin = 0
|
|
fields = []
|
|
|
|
# parse the data
|
|
bins.zip(bits, bitlen).each { |val, off, len|
|
|
off = 32-(off+len)
|
|
msk = (1 << len) - 1
|
|
case val
|
|
when '/', '//', '///' # reserved field, value unspecified
|
|
when /^\d+$/; bin |= val.to_i << off # constant field
|
|
when /^[A-Za-z]+$/
|
|
fld = val.downcase.to_sym
|
|
fld = "#{fld}_".to_sym while $field_mask[fld] and ($field_mask[fld] != msk or $field_shift[fld] != off)
|
|
fields << fld
|
|
$field_mask[fld] ||= msk
|
|
$field_shift[fld] ||= off
|
|
end
|
|
}
|
|
|
|
text.each { |txt|
|
|
# fnabs FRT,FRB (Rc=0)
|
|
curbin = bin
|
|
curfields = fields.dup
|
|
txt.sub!(' Rc=1)', ' (Rc=1)') if txt.include? 'fdiv.' # typo: fdiv. has no '('
|
|
if txt =~ /(.*\S)\s*\((\w+=.*)\)/
|
|
txt = $1
|
|
$2.split.each { |e|
|
|
raise e if e !~ /(\w+)=(\d+)/
|
|
name, val = $1.downcase, $2.to_i
|
|
raise "bad bit #{name} in #{txt}" if not fld = curfields.find { |fld_| fld_.to_s.delete('_') == name }
|
|
curfields.delete fld
|
|
curbin |= val << $field_shift[fld]
|
|
}
|
|
end
|
|
opname, args = txt.split(/\s+/, 2)
|
|
args = args.to_s.downcase.split(/\s*,\s*/).map { |arg| fld = curfields.find { |fld_| fld_.to_s.delete('_') == arg } ; curfields.delete fld ; fld }
|
|
if args.include? nil and curfields.length == 2 and (curfields - [:ra, :d]).empty?
|
|
args[args.index(nil)] = :ra_i16
|
|
curfields.clear
|
|
elsif args.include? nil and curfields.length == 2 and (curfields - [:ra, :ds]).empty?
|
|
args[args.index(nil)] = :ra_i16s
|
|
curfields.clear
|
|
elsif args.include? nil and curfields.length == 2 and (curfields - [:ra, :dq]).empty?
|
|
args[args.index(nil)] = :ra_i16q
|
|
curfields.clear
|
|
elsif args.include? nil and curfields.length == 1
|
|
args[args.index(nil)] = curfields.shift
|
|
end
|
|
raise "bad args #{args.inspect} (#{curfields.inspect}) in #{txt}" if args.include? nil
|
|
$opcodes << [opname, curbin, args]
|
|
|
|
n = (opname.inspect << ',').ljust(10) + '0x%08X' % curbin
|
|
n << ', ' if not args.empty?
|
|
puts "\taddop " + n + args.map { |e| e.inspect }.join(', ')
|
|
}
|
|
end
|
|
|
|
# handle instruction aliases
|
|
# NOT WORKING
|
|
# should be implemented in the parser/displayer instead of opcode list
|
|
# manual work needed for eg conditionnal jumps
|
|
def make_alias(newop, newargs, oldop, oldargs)
|
|
raise "unknown alias #{newop} => #{oldop}" if not op = $opcodes.reverse.find { |op_| op_[0] == oldop }
|
|
op2 = op.dup
|
|
op2[0] = newop
|
|
oldargs.each_with_index { |oa, i|
|
|
# XXX bcctr 4, 6 -> bcctr 4, 6, 0 => not the work
|
|
if oa =~ /^[0-9]+$/ or oa =~ /^0x[0-9a-f]+$/i
|
|
fld = op[2][i]
|
|
op2[1] |= Integer(oa) << $field_shift[fld]
|
|
end
|
|
}
|
|
puts "#\talias #{newop} #{newargs.join(', ')} -> #{oldop} #{oldargs.join(', ')}".downcase
|
|
end
|
|
|
|
require 'enumerator'
|
|
def epilog
|
|
puts "\n\t@field_shift = {"
|
|
puts $field_shift.sort_by { |k, v| k.to_s }.enum_slice(6).map { |slc|
|
|
"\t\t" + slc.map { |k, v| "#{k.inspect} => #{v}" }.join(', ')
|
|
}.join(",\n")
|
|
puts "\t}"
|
|
puts "\n\t@field_mask = {"
|
|
puts $field_mask.sort_by { |k, v| k.to_s }.enum_slice(6).map { |slc|
|
|
"\t\t" + slc.map { |k, v| "#{k.inspect} => #{v > 1000 ? '0x%X' % v : v}" }.join(', ')
|
|
}.join(",\n")
|
|
puts "\t}"
|
|
end
|
|
|
|
$foundop = false
|
|
def parse_page(lines)
|
|
# all instr defining pages include this
|
|
return unless lines.find { |l| l.str =~ /Special Registers Altered|Memory Barrier Instructions|Data Cache Instructions/ } # sync L/dcbt
|
|
|
|
ilist = [] # line buffer
|
|
extended = false
|
|
|
|
# concat lines with same y
|
|
lines = lines.sort_by { |l| [-l.y, l.x] }
|
|
lastline = nil
|
|
lines.delete_if { |l|
|
|
if lastline and lastline.y == l.y and ([lastline.fontx, lastline.fonty] == [l.fontx, l.fonty] or l.str =~ /^\s*$/)
|
|
lastline.str << ' ' << l.str
|
|
true
|
|
else
|
|
lastline = l
|
|
false
|
|
end
|
|
}
|
|
|
|
lines.each { |l|
|
|
# search for the bit indices list
|
|
if l.fonty < 7 and l.str =~ /^0 [\d ]+ 31\s*$/ and (ilist.last.str.split.length == l.str.split.length or ilist.last.str.split.length == l.str.split.length-1)
|
|
$foundop = true
|
|
bitindices = l.str.split.map { |i| i.to_i }
|
|
# previous line is the binary encoding
|
|
encoding = ilist.pop.str.split
|
|
bitindices.pop if encoding.length < bitindices.length
|
|
# previous line is the instruction text format
|
|
ilist.pop if ilist.last.str =~ /\[POWER2? mnemonics?: (.*)\]/
|
|
text = []
|
|
text.unshift l while l = ilist.pop and l = l.str and (l =~ /,|\)$/ or text.empty?)
|
|
ilist = []
|
|
make_instr(encoding, bitindices, text)
|
|
elsif l.str.include? 'Special Registers Altered'
|
|
if not $foundop
|
|
puts ilist.map { |l_| "(#{l_.y}) #{l_.str}" }
|
|
puts lines.map { |l_| "(#{l_.y}) #{l_.str}" } if ilist.empty?
|
|
raise 'nofoundop'
|
|
else
|
|
$foundop = false
|
|
end
|
|
elsif l.str =~ /Extended:\s+Equivalent to:/
|
|
extended = true
|
|
elsif extended
|
|
if l.str.include? ',' and l.str =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*)/ and $opcodes.find { |op| op[0] == $3 }
|
|
newop, newargs, exop, exargs = $1, $2, $3, $4
|
|
make_alias(newop, newargs.split(','), exop, exargs.split(','))
|
|
else extended = false
|
|
end
|
|
else ilist << l
|
|
end
|
|
}
|
|
end
|
|
|
|
# PowerPC Architecture v2.02:
|
|
# 1 - User Instruction Set
|
|
# 2 - Virtual Environment
|
|
# 3 - Operating Environment
|
|
Dir['PPC_Vers202_Book?_public.pdf'].sort.each { |book|
|
|
$stderr.puts book if $stderr.tty?
|
|
pdf = PDF.read book
|
|
pagecount = pdf.trailer['Root']['Pages']['Count'] || 0
|
|
curpage = 0
|
|
pdf.each_page { |p|
|
|
$stderr.print "#{curpage+=1}/#{pagecount} \r" if $stderr.tty?
|
|
p.clip_lines(50, 740)
|
|
list = p.lines.flatten
|
|
|
|
# split columns
|
|
sp1, sp2 = list.partition { |l| l.x < 288 }
|
|
|
|
parse_page(sp1)
|
|
parse_page(sp2)
|
|
}
|
|
$stderr.print " \r" if $stderr.tty?
|
|
}
|
|
|
|
epilog()
|