luajit 2.0 bytecode dump檔案解析
網上搜索好久,沒有找到現成的luajit位元組碼的反編譯工具。
最好的的是NightNord的ljd專案(https://github.com/NightNord/ljd)。但是最近一次提交是1年前。
下載下來之後嘗試了下,報了IO Error:
I/O error while reading dump: Unexpected EOF while trying to read 13550132507160
1275869946770648659091 bytes
雖然luajit自身也有顯示位元組碼的選項(luajit.exe -bl bytecodefile),但是顯示結果相當難懂:
0001 GGET 0 0 ; "ti" 0002 LEN 0 0 0003 ADDVN 0 0 0 ; 1 0004 GGET 1 0 ; "ti" 0005 TNEW 2 0 0006 TSETV 2 1 0 0007 KSHORT 1 0 0008 ADDVN 1 1 0 ; 1 0009 GGET 2 0 ; "ti" 0010 TGETV 2 2 0 0011 TNEW 3 0 0012 TSETV 3 2 1 0013 GGET 2 0 ; "ti" 0014 TGETV 2 2 0 0015 TGETV 2 2 1 0016 KSTR 3 1 ; "鐔勭伅鍚庢湁濡瑰瓙鍦ㄧ敺瀵濇ゼ涓嬪ぇ鍠婂"~
經過努力,終於用python寫出瞭解析luajit位元組碼dump檔案的工具,解析結果為字典樹。程式碼如下:
## according to Bytecode dump format: src/lj_bcdump.h, src/lj_bcwrite.c and other header files import sys import struct import bytecode ERR_SYNTAX = 'command syntax error' ERR_EMPTY_FILE = 'empty file error' ERR_FILE_FORMAT = 'file format incorrect or corrupted file' ERR_SIGNATURE = 'file signature error' ERR_UNSUPPORTED_VERSION = 'unsupported version of bytecode dump' BCDUMP_F_BE = 0x01 BCDUMP_F_STRIP = 0x02 BCDUMP_F_FFI = 0x04 BCDUMP_KGC_CHILD = 0 BCDUMP_KGC_TAB = 1 BCDUMP_KGC_I64 = 2 BCDUMP_KGC_U64 = 3 BCDUMP_KGC_COMPLEX = 4 BCDUMP_KGC_STR = 5 BCDUMP_KTAB_NIL = 0 BCDUMP_KTAB_FALSE = 1 BCDUMP_KTAB_TRUE = 2 BCDUMP_KTAB_INT = 3 BCDUMP_KTAB_NUM = 4 BCDUMP_KTAB_STR = 5 PROTO_CHILD = 0x01 # Has child prototypes PROTO_VARARG = 0x02 # Vararg function PROTO_FFI = 0x04 # Uses BC_KCDATA for FFI datatypes. class ContextStream: def checkedSlice(self, offset, length): self.buffer[offset] if length > 0: self.buffer[offset + length - 1] return self.buffer[offset:offset + length] def __init__(self, buffer, root): self.buffer = buffer self.offset = 0 self.root = root def readULEB128(self): result = 0 length = 1 # at least 1 byte while self.buffer[self.offset + length - 1] >= 128: length = length + 1 tmp = self.readChunk(length) tmp = tmp[::-1] for v in tmp: result = result * 128 + (v & 0b1111111) return result def readByte(self): oldOffset = self.offset self.offset = self.offset + 1 return self.buffer[oldOffset] def readChunk(self, length): result = self.checkedSlice(self.offset, length) self.skip(length) return result def peek(self): return self.buffer[self.offset] def skip(self, length): self.offset = self.offset + length def isBigEndian(self): return (self.root['header']['flags'] & BCDUMP_F_BE) != 0 def isStripped(self): return (self.root['header']['flags'] & BCDUMP_F_STRIP) != 0 def buildFloat64(self, highU32, lowU32): if self.isBigEndian(): buffer = struct.pack('>II', highU32, lowU32) result, = struct.unpack('>d', buffer) return result else: buffer = struct.pack('<II', lowU32, highU32) result, = struct.unpack('<d', buffer) return result def debug(obj): print(repr(obj)) def construct_dump(dump, stream): dump['_offset'] = stream.offset header = {} header['_parent'] = dump dump['header'] = header construct_header(header, stream) protos = [] dump['protos'] = protos while stream.peek() != 0: proto = {} protos.append(proto) proto['_parent'] = dump construct_proto(proto, stream) dump['zero'] = stream.readByte() if dump['zero'] != 0: raise Exception(ERR_FILE_FORMAT) dump['_length'] = stream.offset - dump['_offset'] #dump['_bytes'] = stream.checkedSlice(dump['_offset'], dump['_length'] def construct_header(header, stream): header['_offset'] = stream.offset header['ESC'] = stream.readByte() header['L'] = stream.readByte() header['J'] = stream.readByte() signatureOK = header['ESC'] == 0x1b and header['L'] == 0x4c and header['J'] == 0x4a if not signatureOK: raise Exception(ERR_SIGNATURE) header['version'] = stream.readByte() if header['version'] != 1: raise Exception(ERR_UNSUPPORTED_VERSION) header['flags'] = stream.readULEB128() if not stream.isStripped(): namelen = stream.readULEB128() header['namelen'] = namelen name = stream.readChunk(namelen) header['name'] = name header['_length'] = stream.offset - header['_offset'] #header['_bytes'] = stream.checkedSlice(header['_offset'], header['_length']) def construct_proto(proto, stream): proto['_offset'] = stream.offset proto['length'] = stream.readULEB128() pdata = {} pdata['_parent'] = proto proto['pdata'] = pdata construct_pdata(pdata, stream) proto['_length'] = stream.offset - proto['_offset'] #proto['_bytes'] = stream.checkedSlice(proto['_offset'], proto['_length']) def construct_pdata(pdata, stream): pdata['_offset'] = stream.offset phead = {} phead['_parent'] = pdata pdata['phead'] = phead construct_phead(phead, stream) pdata['bcins'] = stream.readChunk(phead['numbc'] * 4) pdata['uvdata'] = stream.readChunk(phead['numuv'] * 2) kgcs = [] pdata['kgcs'] = kgcs for i in range(phead['numkgc']): kgc = {} kgcs.append(kgc) kgc['_parent'] = pdata construct_kgc(kgc, stream) knums = [] pdata['knums'] = knums for i in range(phead['numkn']): knum = {} knums.append(knum) knum['_parent'] = pdata construct_knum(knum, stream) dump = pdata['_parent']['_parent'] if not stream.isStripped(): pdata['debug'] = stream.readChunk(phead['debuglen']) pdata['_length'] = stream.offset - pdata['_offset'] #pdata['_bytes'] = stream.checkedSlice(pdata['_offset'], pdata['_length']) def construct_kgc(kgc, stream): kgc['_offset'] = stream.offset kgctype = stream.readULEB128() kgc['kgctype'] = kgctype if kgctype == BCDUMP_KGC_CHILD: pass elif kgctype == BCDUMP_KGC_TAB: ktab = {} ktab['_parent'] = kgc kgc['ktab'] = ktab construct_ktab(ktab, stream) elif kgctype == BCDUMP_KGC_I64: i64 = {} i64['_parent'] = kgc kgc['i64'] = i64 lo = stream.readULEB128() i64['lo'] = lo hi = stream.readULEB128() i64['hi'] = hi i64['value'] = lo + hi * (2 ** 32) if i64['value'] >= (2 ** 63): i64['value'] = i64['value'] - (2 ** 64) elif kgctype == BCDUMP_KGC_U64: u64 = {} u64['_parent'] = kgc kgc['u64'] = u64 lo = stream.readULEB128() u64['lo'] = lo hi = stream.readULEB128() u64['hi'] = hi u64['value'] = lo + hi * (2 ** 32) elif kgctype == BCDUMP_KGC_COMPLEX: complex = {} complex['_parent'] = kgc kgc['complex'] = complex rlo = stream.readULEB128() complex['rlo'] = rlo rhi = stream.readULEB128() complex['rhi'] = rhi complex['real'] = stream.buildFloat64(rhi, rlo) ilo = stream.readULEB128() complex['ilo'] = ilo ihi = stream.readULEB128() complex['ihi'] = ihi complex['image'] = stream.buildFloat64(ihi, ilo) elif kgctype >= BCDUMP_KGC_STR: strlen = kgctype - BCDUMP_KGC_STR kgc['str'] = stream.readChunk(strlen) kgc['_length'] = stream.offset - kgc['_offset'] kgc['_bytes'] = stream.checkedSlice(kgc['_offset'], kgc['_length']) def construct_ktab(ktab, stream): ktab['_offset'] = stream.offset ktab['narray'] = stream.readULEB128() ktab['nhash'] = stream.readULEB128() karrays = [] ktab['karrays'] = karrays for i in range(ktab['narray']): karray = {} karray['_parent'] = ktab karrays.append(karray) construct_karray(karray, stream) khashes = [] ktab['khashes'] = khashes for i in range(ktab['nhash']): khash = {} khash['_parent'] = ktab khashes.append(khash) construct_khash(khash, stream) ktab['_length'] = stream.offset - ktab['_offset'] #ktab['_bytes'] = stream.checkedSlice(ktab['_offset'], ktab['_length']) def construct_karray(karray, stream): karray['_offset'] = stream.offset ktabk = {} ktabk['_parent'] = karray karray['value'] = ktabk construct_ktabk(ktabk, stream) karray['_length'] = stream.offset - karray['_offset'] #karray['_bytes'] = stream.checkedSlice(karray['_offset'], karray['_length']) def construct_khash(khash, stream): khash['_offset'] = stream.offset key = {} key['_parent'] = khash khash['key'] = key construct_ktabk(key, stream) value = {} value['_parent'] = khash khash['value'] = value construct_ktabk(value, stream) khash['_length'] = stream.offset - khash['_offset'] khash['_bytes'] = stream.checkedSlice(khash['_offset'], khash['_length']) def construct_ktabk(ktabk, stream): ktabk['_offset'] = stream.offset ktabtype = stream.readULEB128() ktabk['ktabtype'] = ktabtype if ktabtype == BCDUMP_KTAB_NIL: ktabk['value'] = None elif ktabtype == BCDUMP_KTAB_FALSE: ktabk['value'] = False elif ktabtype == BCDUMP_KTAB_TRUE: ktabk['value'] = True elif ktabtype == BCDUMP_KTAB_NUM: lo = stream.readULEB128() ktabk['lo'] = lo hi = stream.readULEB128() ktabk['hi'] = hi ktabk['value'] = stream.buildFloat64(hi, lo) elif ktabtype == BCDUMP_KTAB_INT: int = stream.readULEB128() ktabk['int'] = int if int >= (2 ** 31): ktabk['value'] = int - (2 ** 32) else: ktabk['value'] = int elif ktabtype >= BCDUMP_KTAB_STR: strlen = ktabtype - BCDUMP_KTAB_STR ktabk['str'] = stream.readChunk(strlen) ktabk['_length'] = stream.offset - ktabk['_offset'] ktabk['_bytes'] = stream.checkedSlice(ktabk['_offset'], ktabk['_length']) def construct_knum(knum, stream): knum['_offset'] = stream.offset isnum = (stream.peek() & 1) != 0 lo= stream.readULEB128() lo = lo >> 1 # get top 32 bit if isnum: knum['lo'] = lo hi = stream.readULEB128() knum['hi'] = hi knum['value'] = stream.buildFloat64(hi, lo) else: knum['int'] = lo if lo >= (2 ** 31): knum['value'] = lo - (2 ** 32) else: knum['value'] = lo knum['_length'] = stream.offset - knum['_offset'] knum['_bytes'] = stream.checkedSlice(knum['_offset'], knum['_length']) def construct_phead(phead, stream): phead['_offset'] = stream.offset phead['flags'] = stream.readByte() phead['numparams'] = stream.readByte() phead['framesize'] = stream.readByte() phead['numuv'] = stream.readByte() phead['numkgc'] = stream.readULEB128() phead['numkn'] = stream.readULEB128() phead['numbc'] = stream.readULEB128() if not stream.isStripped(): phead['debuglen'] = stream.readULEB128() if phead['debuglen'] != 0: phead['firstline'] = stream.readULEB128() phead['numline'] = stream.readULEB128() phead['_length'] = stream.offset - phead['_offset'] phead['_bytes'] = stream.checkedSlice(phead['_offset'], phead['_length']) def decode(filename): buffer = None with open(filename, 'rb') as f: buffer = f.read() if buffer is None: raise Exception(ERR_EMPTY_FILE) try: dump = {} stream = ContextStream(buffer, dump) dump['_parent'] = None construct_dump(dump, stream) return dump,stream except IndexError: raise Exception(ERR_FILE_FORMAT) if __name__ == '__main__': if len(sys.argv) != 2: print('usage:\ndecode.py file') raise Exception(ERR_SYNTAX) else: dump,context = decode(sys.argv[1]) # debug(dump) i = 0 for proto in dump['protos']: print('-- proto[{0}] --'.format(i)) i = i + 1 bytecode.printbytecode(context, proto)
其中的鍵值命名基本上是對照著LuaJIT 2.0 Bytecode Dump Format ( http://wiki.luajit.org/Bytecode-2.0#LuaJIT-2.0-Bytecode-Dump-Format )來的。
針對由大量字串常量資源構成的位元組碼dump檔案,處理起來基本上沒問題。
列印修飾過後的位元組碼指令程式碼如下:
import struct import decode string_encoding = 'utf-8' GGET = 52 LEN = 19 ADDVN = 20 TNEW = 50 TSETV = 57 KSHORT = 39 TGETV = 54 KSTR = 37 TSETB = 59 TGETB = 56 RET0 = 71 RET1 = 72 RET = 70 FNEW = 49 GSET = 53 MOV = 16 CALL = 62 SUBVV = 31 table = {} op = {} op['name'] = 'SUBVV' op['format'] = 'abc' op['A'] = 'dst' op['B'] = 'var' op['C'] = 'var' op['description'] = '{A} = {B} - {C}' table[SUBVV] = op op = {} op['name'] = 'RET1' op['format'] = 'ad' op['A'] = 'rbase' op['D'] = 'lit' op['description'] = 'return local[{A}]' table[RET1] = op op = {} op['name'] = 'CALL' op['format'] = 'abc' op['A'] = 'base' op['B'] = 'lit' op['C'] = 'lit' op['description'] = 'local[{A}, ..., {A}+{B}-2] = local[{A}]( local[{A}+1, ..., {A}+{C}-1] )' table[CALL] = op op = {} op['name'] = 'MOV' op['format'] = 'ad' op['A'] = 'dst' op['D'] = 'var' op['description'] = '{A} = {D}' table[MOV] = op op = {} op['name'] = 'GSET' op['format'] = 'ad' op['A'] = 'var' op['D'] = 'str' op['description'] = '_G[{D}] = {A}' table[GSET] = op op = {} op['name'] = 'FNEW' op['format'] = 'ad' op['A'] = 'dst' op['D'] = 'func' op['description'] = '{A} = function[{D}]' table[FNEW] = op op = {} op['name'] = 'RET' op['format'] = 'ad' op['A'] = 'rbase' op['D'] = 'lit' op['description'] = 'return slot[{A}, ..., {A}+{D}-2]' table[RET] = op op = {} op['name'] = 'RET0' op['format'] = 'ad' op['A'] = 'rbase' op['D'] = 'lit' op['description'] = 'return' table[RET0] = op op = {} op['name'] = 'TGETB' op['format'] = 'abc' op['A'] = 'dst' op['B'] = 'var' op['C'] = 'lit' op['description'] = '{A} = {B}[{C}]' table[TGETB] = op op = {} op['name'] = 'TSETB' op['format'] = 'abc' op['A'] = 'var' op['B'] = 'var' op['C'] = 'lit' op['description'] = '{B}[{C}] = {A}' table[TSETB] = op op = {} op['name'] = 'KSTR' op['format'] = 'ad' op['A'] = 'dst' op['D'] = 'str' op['description'] = '{A} = {D}' table[KSTR] = op op = {} op['name'] = 'TGETV' op['format'] = 'abc' op['A'] = 'dst' op['B'] = 'var' op['C'] = 'var' op['description'] = '{A} = {B}[{C}]' table[TGETV] = op op = {} op['name'] = 'KSHORT' op['format'] = 'ad' op['A'] = 'dst' op['D'] = 'lits' op['description'] = '{A} = {D}' table[KSHORT] = op op = {} op['name'] = 'TSETV' op['format'] = 'abc' op['A'] = 'var' op['B'] = 'var' op['C'] = 'var' op['description'] = '{B}[{C}] = {A}' table[TSETV] = op op = {} op['name'] = 'GGET' op['format'] = 'ad' op['A'] = 'dst' op['D'] = 'str' op['description'] = '{A} = _G[{D}]' table[GGET] = op op = {} op['name'] = 'LEN' op['format'] = 'ad' op['A'] = 'dst' op['D'] = 'var' op['description'] = '{A} = len({D})' table[LEN] = op op = {} op['name'] = 'ADDVN' op['format'] = 'abc' op['A'] = 'dst' op['B'] = 'var' op['C'] = 'num' op['description'] = '{A} = {B} + {C}' table[ADDVN] = op op = {} op['name'] = 'TNEW' op['format'] = 'ad' op['A'] = 'dst' op['D'] = 'lit' op['description'] = '{A} = {{}}' table[TNEW] = op def toU8(buffer): return buffer[0] def toI16(buffer, context): if context.isBigEndian(): result, = struct.unpack('>h', buffer) else: result, = struct.unpack('<h', buffer) return result def toU16(buffer, context): if context.isBigEndian(): result, = struct.unpack('>H', buffer) else: result, = struct.unpack('<H', buffer) return result def format(fmt, value, context, proto): kgcs = proto['pdata']['kgcs'] knums = proto['pdata']['knums'] phead = proto['pdata']['phead'] if fmt == 'dst': return 'slot[' + str(value) + ']' elif fmt == 'var': return 'slot[' + str(value) + ']' elif fmt == 'lit': return str(value) elif fmt == 'lits': return str(value) elif fmt == 'base': return str(value) elif fmt == 'rbase': return str(value) elif fmt == 'func': # wtf the negated index of constants? return str(value//2) # hack elif fmt == 'str': index = phead['numkgc'] - value - 1 strLiteral = kgcs[index]['str'] return repr(strLiteral.decode(string_encoding)) elif fmt == 'num': index = value numLiteral = knums[index]['value'] return repr(numLiteral) else: raise Exception('unknow operand format') def formatArgumentsAndReturns(op, s): if op in [RET, RET1]: startIndex = s.index('[') endIndex = s.index(']', startIndex + 1) splits = s[startIndex: endIndex + 1] lst = eval(splits) if len(lst) == 3: return 'return local' + repr(list(range(lst[0], lst[2] + 1))) else: return s elif op in [CALL]: result = '' startIndex = 0 while True: prev = startIndex startIndex = s.find('local[', startIndex) if startIndex == -1: result = result + s[prev:] break startIndex = startIndex + len('local') result = result + s[prev: startIndex] endIndex = s.find(']', startIndex) splits = s[startIndex: endIndex + 1] lst = eval(splits) if '...' in splits and len(lst) == 3: lst = list(range(lst[0], lst[2] + 1)) result = result + repr(lst) else: result = result + splits startIndex = endIndex + 1 if result.startswith('local[] = '): result = result[len('local[] = '):] return result else: return s def formatbytecode(op, A, B, C, D, context, proto): op = op[0] opinfo = table[op] A = format(opinfo['A'], toU8(A), context, proto) if opinfo['format'] == 'ad': D = format(opinfo['D'], toU16(D, context), context, proto) else: B = format(opinfo['B'], toU8(B), context, proto) C = format(opinfo['C'], toU8(C), context, proto) result = opinfo['description'].format(A=A, B=B, C=C, D=D) result = formatArgumentsAndReturns(op, result) return result def printbytecode(context, proto): pdata = proto['pdata'] phead = pdata['phead'] bcins = pdata['bcins'] numbc = phead['numbc'] for i in range(numbc): seq = i + 1 ins = bcins[i*4:i*4+4] if context.isBigEndian(): op = ins[3:4] A = ins[2:3] B = ins[0:1] C = ins[1:2] D = ins[0:2] else: op = ins[0:1] A = ins[1:2] B = ins[3:4] C = ins[2:3] D = ins[2:4] insSeq = str(seq).zfill(4) print(insSeq, ' ', end='') print(formatbytecode(op, A, B, C, D, context, proto), end='') print()
輸出效果如下:
C:\Users\敏\Desktop>decode q12.bc
-- proto[0] --
0001 slot[0] = _G['ti']
0002 slot[0] = len(slot[0])
0003 slot[0] = slot[0] + 1
0004 slot[1] = _G['ti']
0005 slot[2] = {}
0006 slot[1][slot[0]] = slot[2]
0007 slot[1] = 0
0008 slot[1] = slot[1] + 1
0009 slot[2] = _G['ti']
0010 slot[2] = slot[2][slot[0]]
0011 slot[3] = {}
0012 slot[2][slot[1]] = slot[3]
0013 slot[2] = _G['ti']
0014 slot[2] = slot[2][slot[0]]
0015 slot[2] = slot[2][slot[1]]
0016 slot[3] = '大街上,有美女走過來主動吻你,你該怎麼辦'
0017 slot[2][1] = slot[3]
0018 slot[2] = _G['ti']
0019 slot[2] = slot[2][slot[0]]
0020 slot[2] = slot[2][slot[1]]
0021 slot[3] = {}
0022 slot[2][2] = slot[3]
0023 slot[2] = _G['ti']
0024 slot[2] = slot[2][slot[0]]
0025 slot[2] = slot[2][slot[1]]
0026 slot[2] = slot[2][2]
0027 slot[3] = '問問是否吻錯人'
0028 slot[2][1] = slot[3]
0029 slot[2] = _G['ti']
0030 slot[2] = slot[2][slot[0]]
0031 slot[2] = slot[2][slot[1]]
0032 slot[2] = slot[2][2]
0033 slot[3] = 1
0034 slot[2][2] = slot[3]
0035 slot[2] = _G['ti']
0036 slot[2] = slot[2][slot[0]]
0037 slot[2] = slot[2][slot[1]]
0038 slot[3] = {}
0039 slot[2][3] = slot[3]
0040 slot[2] = _G['ti']
0041 slot[2] = slot[2][slot[0]]
0042 slot[2] = slot[2][slot[1]]
0043 slot[2] = slot[2][3]
0044 slot[3] = '反正是她主動的'
0045 slot[2][1] = slot[3]
0046 slot[2] = _G['ti']
0047 slot[2] = slot[2][slot[0]]
0048 slot[2] = slot[2][slot[1]]
0049 slot[2] = slot[2][3]
0050 slot[3] = 0
0051 slot[2][2] = slot[3]
0052 slot[2] = _G['ti']
0053 slot[2] = slot[2][slot[0]]
0054 slot[2] = slot[2][slot[1]]
0055 slot[3] = {}
0056 slot[2][4] = slot[3]
0057 slot[2] = _G['ti']
0058 slot[2] = slot[2][slot[0]]
0059 slot[2] = slot[2][slot[1]]
0060 slot[2] = slot[2][4]
0061 slot[3] = '美女遇到麻煩事了'
0062 slot[2][1] = slot[3]
0063 slot[2] = _G['ti']
0064 slot[2] = slot[2][slot[0]]
0065 slot[2] = slot[2][slot[1]]
0066 slot[2] = slot[2][4]
0067 slot[3] = 0
0068 slot[2][2] = slot[3]
0069 slot[2] = _G['ti']
0070 slot[2] = slot[2][slot[0]]
0071 slot[2] = slot[2][slot[1]]
0072 slot[3] = {}
0073 slot[2][5] = slot[3]
0074 slot[2] = _G['ti']
0075 slot[2] = slot[2][slot[0]]
0076 slot[2] = slot[2][slot[1]]
0077 slot[2] = slot[2][5]
0078 slot[3] = '吻更狠,直接舌吻'
0079 slot[2][1] = slot[3]
0080 slot[2] = _G['ti']
0081 slot[2] = slot[2][slot[0]]
0082 slot[2] = slot[2][slot[1]]
0083 slot[2] = slot[2][5]
0084 slot[3] = 0
0085 slot[2][2] = slot[3]
0086 return
而其lua原始碼為:
local i = #ti + 1
ti[i] = {}
local j = 0
--題目
j = j + 1
ti[i][j] = {}
ti[i][j][1] = "大街上,有美女走過來主動吻你,你該怎麼辦"
ti[i][j][2] = {}
ti[i][j][2][1] = "問問是否吻錯人"
ti[i][j][2][2] = 1
ti[i][j][3] = {}
ti[i][j][3][1] = "反正是她主動的"
ti[i][j][3][2] = 0
ti[i][j][4] = {}
ti[i][j][4][1] = "美女遇到麻煩事了"
ti[i][j][4][2] = 0
ti[i][j][5] = {}
ti[i][j][5][1] = "吻更狠,直接舌吻"
ti[i][j][5][2] = 0
如果資源配置檔案裡含有更復雜的指令那就麻煩了。
但是既然是資源配置檔案,那麼就不可能很複雜。解析下字串常量、整數常量、浮點數常量等應該是足夠了。
相關推薦
luajit 2.0 bytecode dump檔案解析
網上搜索好久,沒有找到現成的luajit位元組碼的反編譯工具。 最好的的是NightNord的ljd專案(https://github.com/NightNord/ljd)。但是最近一次提交是1年前。 下載下來之後嘗試了下,報了IO Error: I/O error wh
dump檔案解析之探索.Net的記憶體
前言: 對於需要長時間執行的.net程式,有時需要我們檢視記憶體的使用有沒有記憶體洩露問題。 我們可以從dump檔案中找到答案。 Dump的看點 用dump檔案來分析記憶體,到底我們需要關心哪些點呢? 記憶體的使用情況 HeapSize/object的數量 也就是託管堆使用大小以及託管堆內有
ARKit學習之2.0基礎及案例解析(後續再更新)
為了方便AR開發交流,博主建立了一個群 :891555732,歡迎一起討論 一.多人共享資料 官方案例原始碼 : https://github.com/Unity-Technologies/SharedSpheres ①.獲取資料及儲
UCloud釋出“資料方舟2.0” 強力防範檔案誤刪與勒索病毒攻擊
2017年1月14日某網際網路公司因發生故障導致資料損壞而重啟伺服器,並且由於相關備份資料庫也同時故障所以經多次嘗試最終仍舊無法修復成功。 2017年2月1日凌晨,著名的程式碼資源託管網站 Gitlab.com 的一位工程師在維護資料時不慎刪除約 300GB 的資料。當他意識到並按下 Ctrl +
Django 2.0 專案配置檔案介紹:
Django專案配置檔案為setting,如下: 其各部分內容含義如下: BASE_DIR:專案檔案路徑,專案建立之後自動設定,不需要改動。 SECRET_KEY:金鑰,專案建立之後自動設定,不需要改動。 DEBUG:供開發人員用,專案結束後,需要將其設定為False。
spring boot 2.0的POM檔案中不用使用pluginManagement標籤,會使打包配置檔案失效
參考如下,如果spring boot 2.0中的採用下面模式 <pluginManagement> <plugins> <plugin> &nb
java使用Extract API 2.0生成.hyper檔案
突然接到一個任務,需要用java使用Extract API 2.0實現抽取資料庫的數並生成.hyper檔案。沒辦法,只能一邊看文件一邊寫個demo先試試看能不能行得通。 文件:https://onlinehelp.tableau.com/current/api/extract_api/e
微服務 SpringBoot 2.0(二):配置檔案解析
properties我用了好多年,你卻讓我用yml,這是什麼鬼 —— Java面試必修 引言 上一篇介紹了Spring Boot的輕鬆入門專案構建,對Spring Boot的特性有了初步瞭解。但如果要想玩得很熟練的話就請看接下來的文章,這樣有助於後續我們快速
Ansible API 2.0解析
ansibleimport json from collections import namedtuple from ansible.parsing.dataloader import DataLoader from ansible.vars import VariableManager from ansib
關於MBI-MFC理財平臺2.0最新制度解析
風控 模式 必須 賬戶 現在 粉絲 100% 什麽是 官方 MFC 2.0版本解讀由於現在平臺的數據過於龐大平臺決定將龐大的數據進行壓縮 如何壓縮?平臺將會以粉絲戶的配送次數進行分配壓縮 【壓縮分配】配送?次 - 80%分配到資產包;20%分配到GRC1錢包配送?次 - 7
微服務 SpringBoot 2.0(二):配置文件解析
@override 書寫 string 接下來 code java interface sse als properties我用了好多年,你卻讓我用yml,這是什麽鬼 —— Java面試必修 引言 上一篇介紹了Spring Boot的輕松入門項目構建,對Spring B
2.0 解析系列 | OceanBase 2.0 之 Flashback功能
OB君:本文是 “OceanBase 2.0 技術解析系列” 的第六篇文章。今天我們來聊聊資料的持續可用,說說2.0中大家都很關心的“Flashback”閃回功能。更多精彩歡迎關注OceanBase公眾號持續訂閱本系列內容! 前言 資料庫產品作為資訊系統的重要組成部分,除了要高效的處理使用者請求
asp.net 2.0 分析器錯誤訊息: 檔案.aspx.cs”不存在錯誤
布webapplication時後老是報告分析器錯誤訊息: 檔案.aspx.cs”不存在錯誤,差點抓狂,後來在網上搜到原因是: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="{0}" Inherits="{1}" %&g
2.0解析系列 | OceanBase 2.0 之 Flashback 閃回功能
OB君:本文是 “ OceanBase 2.0 技術解析系列” 的第六篇文章。今天我們來聊聊資料的持續可用,說說2.0中大家都很關心的“Flashback”閃回功能。更多精彩歡迎關注OceanBase公眾號持續訂閱本系列內容! 前言 資料庫產品作為資訊
2.0解析系列 | OceanBase 2.0 之 索引實時生效
OB君:本文是 “ OceanBase 2.0 技術解析系列” 的第七篇文章。今天我們來聊聊資料的持續可用,說說2.0的索引實時生效功能。更多精彩歡迎關注OceanBase公眾號持續訂閱本系列內容! 引言 隨著業務的快速發展,其對資料庫的資料訪問規則是不斷變化的,在資料庫中新建索引來加速業務查
2.0解析系列 | OceanBase 2.0——第一款支援“儲存過程”的原生分散式資料庫
OB君:本文是 “ OceanBase 2.0 技術解析系列” 的第八篇文章,今天我們來說說2.0版本最標誌性、最不得不提的新特性——儲存過程。在為數不多的原生分散式資料庫中,OceanBase 2.0是第一款支援儲存過程功能的產品。本文將為你深入剖析2.0中儲存過程的功能特性和實現機制。更多精彩歡迎關
未能載入檔案或程式集 System Web Helpers Version 2 0 0 0
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
2.0 解析系列 | OceanBase的重要基礎設施——DBReplay
OB君:9月21日,OceanBase 2.0 在雲棲大會上重磅釋出。我們將在接下來的時間裡為大家持續推出 “ OceanBase 2.0 技術解析系列” 文章,分別從 可運維性、分散式架構、資料可用性、價效比及相容性 五個方面對OceanBase 2.0的產品新特性及其背後的技術原理進行深入的解析。
JavaEE-SSM:005 Mybatis的配置檔案解析(2)
setting配置不常用,給出配置說明: 本文給出Setting配置的全量程式碼: <settings> <setting name="cacheEnabled" value="true"
.net core 2.0 虛擬目錄下載 Android Apk 等檔案
當Android 檔案 Apk 放在Asp.net core wwwroot 虛擬目錄下面、訪問是 404,設定Content-Type型別 app.UseStaticFiles(); //設定實際目錄與虛擬目錄