#---------------------------------------------------------------------
# IDAPython - Python plugin for Interactive Disassembler
#
# Copyright (c) 2004-2010 Gergely Erdelyi <gergely.erdelyi@d-dome.net>
#
# All rights reserved.
#
# For detailed copyright information see the file COPYING in
# the root of the distribution archive.
#---------------------------------------------------------------------
"""
idautils.py - High level utility functions for IDA
"""
import idaapi
import idc
import types
import os def refs(ea, funcfirst, funcnext):
"""
Generic reference collector - INTERNAL USE ONLY.
"""
ref = funcfirst(ea)
while ref != idaapi.BADADDR:
yield ref
ref = funcnext(ea, ref) def CodeRefsTo(ea, flow):
"""
Get a list of code references to 'ea' @param ea: Target address
@param flow: Follow normal code flow or not
@type flow: Boolean (0/1, False/True) @return: list of references (may be empty list) Example:: for ref in CodeRefsTo(ScreenEA(), 1):
print ref
"""
if flow == 1:
return refs(ea, idaapi.get_first_cref_to, idaapi.get_next_cref_to)
else:
return refs(ea, idaapi.get_first_fcref_to, idaapi.get_next_fcref_to) def CodeRefsFrom(ea, flow):
"""
Get a list of code references from 'ea' @param ea: Target address
@param flow: Follow normal code flow or not
@type flow: Boolean (0/1, False/True) @return: list of references (may be empty list) Example:: for ref in CodeRefsFrom(ScreenEA(), 1):
print ref
"""
if flow == 1:
return refs(ea, idaapi.get_first_cref_from, idaapi.get_next_cref_from)
else:
return refs(ea, idaapi.get_first_fcref_from, idaapi.get_next_fcref_from) def DataRefsTo(ea):
"""
Get a list of data references to 'ea' @param ea: Target address @return: list of references (may be empty list) Example:: for ref in DataRefsTo(ScreenEA()):
print ref
"""
return refs(ea, idaapi.get_first_dref_to, idaapi.get_next_dref_to) def DataRefsFrom(ea):
"""
Get a list of data references from 'ea' @param ea: Target address @return: list of references (may be empty list) Example:: for ref in DataRefsFrom(ScreenEA()):
print ref
"""
return refs(ea, idaapi.get_first_dref_from, idaapi.get_next_dref_from) def XrefTypeName(typecode):
"""
Convert cross-reference type codes to readable names @param typecode: cross-reference type code
"""
ref_types = {
0 : 'Data_Unknown',
1 : 'Data_Offset',
2 : 'Data_Write',
3 : 'Data_Read',
4 : 'Data_Text',
5 : 'Data_Informational',
16 : 'Code_Far_Call',
17 : 'Code_Near_Call',
18 : 'Code_Far_Jump',
19 : 'Code_Near_Jump',
20 : 'Code_User',
21 : 'Ordinary_Flow'
}
assert typecode in ref_types, "unknown reference type %d" % typecode
return ref_types[typecode] def _copy_xref(xref):
""" Make a private copy of the xref class to preserve its contents """
class _xref(object):
pass xr = _xref()
for attr in [ 'frm', 'to', 'iscode', 'type', 'user' ]:
setattr(xr, attr, getattr(xref, attr))
return xr def XrefsFrom(ea, flags=0):
"""
Return all references from address 'ea' @param ea: Reference address
@param flags: any of idaapi.XREF_* flags Example::
for xref in XrefsFrom(here(), 0):
print xref.type, XrefTypeName(xref.type), \
'from', hex(xref.frm), 'to', hex(xref.to)
"""
xref = idaapi.xrefblk_t()
if xref.first_from(ea, flags):
yield _copy_xref(xref)
while xref.next_from():
yield _copy_xref(xref) def XrefsTo(ea, flags=0):
"""
Return all references to address 'ea' @param ea: Reference address
@param flags: any of idaapi.XREF_* flags Example::
for xref in XrefsTo(here(), 0):
print xref.type, XrefTypeName(xref.type), \
'from', hex(xref.frm), 'to', hex(xref.to)
"""
xref = idaapi.xrefblk_t()
if xref.first_to(ea, flags):
yield _copy_xref(xref)
while xref.next_to():
yield _copy_xref(xref) def Threads():
"""Returns all thread IDs"""
for i in xrange(0, idc.GetThreadQty()):
yield idc.GetThreadId(i) def Heads(start=None, end=None):
"""
Get a list of heads (instructions or data) @param start: start address (default: inf.minEA)
@param end: end address (default: inf.maxEA) @return: list of heads between start and end
"""
if not start: start = idaapi.cvar.inf.minEA
if not end: end = idaapi.cvar.inf.maxEA ea = start
if not idc.isHead(idc.GetFlags(ea)):
ea = idaapi.next_head(ea, end)
while ea != idaapi.BADADDR:
yield ea
ea = idaapi.next_head(ea, end) def Functions(start=None, end=None):
"""
Get a list of functions @param start: start address (default: inf.minEA)
@param end: end address (default: inf.maxEA) @return: list of heads between start and end @note: The last function that starts before 'end' is included even
if it extends beyond 'end'. Any function that has its chunks scattered
in multiple segments will be reported multiple times, once in each segment
as they are listed.
"""
if not start: start = idaapi.cvar.inf.minEA
if not end: end = idaapi.cvar.inf.maxEA # find first function head chunk in the range
chunk = idaapi.get_fchunk(start)
if not chunk:
chunk = idaapi.get_next_fchunk(start)
while chunk and chunk.startEA < end and (chunk.flags & idaapi.FUNC_TAIL) != 0:
chunk = idaapi.get_next_fchunk(chunk.startEA)
func = chunk while func and func.startEA < end:
startea = func.startEA
yield startea
func = idaapi.get_next_func(startea) def Chunks(start):
"""
Get a list of function chunks @param start: address of the function @return: list of funcion chunks (tuples of the form (start_ea, end_ea))
belonging to the function
"""
func_iter = idaapi.func_tail_iterator_t( idaapi.get_func( start ) )
status = func_iter.main()
while status:
chunk = func_iter.chunk()
yield (chunk.startEA, chunk.endEA)
status = func_iter.next() def Modules():
"""
Returns a list of module objects with name,size,base and the rebase_to attributes
"""
mod = idaapi.module_info_t()
result = idaapi.get_first_module(mod)
while result:
yield idaapi.object_t(name=mod.name, size=mod.size, base=mod.base, rebase_to=mod.rebase_to)
result = idaapi.get_next_module(mod) def Names():
"""
Returns a list of names @return: List of tuples (ea, name)
"""
for i in xrange(idaapi.get_nlist_size()):
ea = idaapi.get_nlist_ea(i)
name = idaapi.get_nlist_name(i)
yield (ea, name) def Segments():
"""
Get list of segments (sections) in the binary image @return: List of segment start addresses.
"""
for n in xrange(idaapi.get_segm_qty()):
seg = idaapi.getnseg(n)
if seg:
yield seg.startEA def Entries():
"""
Returns a list of entry points @return: List of tuples (index, ordinal, ea, name)
"""
n = idaapi.get_entry_qty()
for i in xrange(0, n):
ordinal = idaapi.get_entry_ordinal(i)
ea = idaapi.get_entry(ordinal)
name = idaapi.get_entry_name(ordinal)
yield (i, ordinal, ea, name) def FuncItems(start):
"""
Get a list of function items @param start: address of the function @return: ea of each item in the function
"""
func = idaapi.get_func(start)
if not func:
return
fii = idaapi.func_item_iterator_t()
ok = fii.set(func)
while ok:
yield fii.current()
ok = fii.next_code() def Structs():
"""
Get a list of structures @return: List of tuples (idx, sid, name)
"""
idx = idc.GetFirstStrucIdx()
while idx != idaapi.BADADDR:
sid = idc.GetStrucId(idx)
yield (idx, sid, idc.GetStrucName(sid))
idx = idc.GetNextStrucIdx(idx) def StructMembers(sid):
"""
Get a list of structure members information (or stack vars if given a frame). @param sid: ID of the structure. @return: List of tuples (offset, name, size) @note: If 'sid' does not refer to a valid structure,
an exception will be raised.
@note: This will not return 'holes' in structures/stack frames;
it only returns defined structure members.
"""
m = idc.GetFirstMember(sid)
if m == -1:
raise Exception("No structure with ID: 0x%x" % sid)
while (m != idaapi.BADADDR):
name = idc.GetMemberName(sid, m)
if name:
yield (m, name, idc.GetMemberSize(sid, m))
m = idc.GetStrucNextOff(sid, m) def DecodePrecedingInstruction(ea):
"""
Decode preceding instruction in the execution flow. @param ea: address to decode
@return: (None or the decode instruction, farref)
farref will contain 'true' if followed an xref, false otherwise
"""
prev_addr, farref = idaapi.decode_preceding_insn(ea)
if prev_addr == idaapi.BADADDR:
return (None, False)
else:
return (idaapi.cmd.copy(), farref) def DecodePreviousInstruction(ea):
"""
Decodes the previous instruction and returns an insn_t like class @param ea: address to decode
@return: None or a new insn_t instance
"""
inslen = idaapi.decode_prev_insn(ea)
if inslen == 0:
return None return idaapi.cmd.copy() def DecodeInstruction(ea):
"""
Decodes an instruction and returns an insn_t like class @param ea: address to decode
@return: None or a new insn_t instance
"""
inslen = idaapi.decode_insn(ea)
if inslen == 0:
return None return idaapi.cmd.copy() def GetDataList(ea, count, itemsize=1):
"""
Get data list - INTERNAL USE ONLY
"""
if itemsize == 1:
getdata = idaapi.get_byte
elif itemsize == 2:
getdata = idaapi.get_word
elif itemsize == 4:
getdata = idaapi.get_long
elif itemsize == 8:
getdata = idaapi.get_qword
else:
raise ValueError, "Invalid data size! Must be 1, 2, 4 or 8" endea = ea + itemsize * count
curea = ea
while curea < endea:
yield getdata(curea)
curea += itemsize def PutDataList(ea, datalist, itemsize=1):
"""
Put data list - INTERNAL USE ONLY
"""
putdata = None if itemsize == 1:
putdata = idaapi.patch_byte
if itemsize == 2:
putdata = idaapi.patch_word
if itemsize == 4:
putdata = idaapi.patch_long assert putdata, "Invalid data size! Must be 1, 2 or 4" for val in datalist:
putdata(ea, val)
ea = ea + itemsize def MapDataList(ea, length, func, wordsize=1):
"""
Map through a list of data words in the database @param ea: start address
@param length: number of words to map
@param func: mapping function
@param wordsize: size of words to map [default: 1 byte] @return: None
"""
PutDataList(ea, map(func, GetDataList(ea, length, wordsize)), wordsize) def GetInputFileMD5():
"""
Return the MD5 hash of the input binary file @return: MD5 string or None on error
"""
return idc.GetInputMD5() class Strings(object):
"""
Returns the string list. Example:
s = Strings() for i in s:
print "%x: len=%d type=%d -> '%s'" % (i.ea, i.length, i.type, str(i)) """
class StringItem(object):
"""
Class representing each string item.
"""
def __init__(self, si):
self.ea = si.ea
"""String ea"""
self.type = si.type
"""string type (ASCSTR_xxxxx)"""
self.length = si.length
"""string length""" def __str__(self):
return idc.GetString(self.ea, self.length, self.type) STR_C = 0x0001
"""C-style ASCII string"""
STR_PASCAL = 0x0002
"""Pascal-style ASCII string (length byte)"""
STR_LEN2 = 0x0004
"""Pascal-style, length is 2 bytes"""
STR_UNICODE = 0x0008
"""Unicode string"""
STR_LEN4 = 0x0010
"""Pascal-style, length is 4 bytes"""
STR_ULEN2 = 0x0020
"""Pascal-style Unicode, length is 2 bytes"""
STR_ULEN4 = 0x0040
"""Pascal-style Unicode, length is 4 bytes""" def clear_cache(self):
"""Clears the strings list cache"""
self.refresh(0, 0) # when ea1=ea2 the kernel will clear the cache def __init__(self, default_setup = True):
"""
Initializes the Strings enumeration helper class @param default_setup: Set to True to use default setup (C strings, min len 5, ...)
"""
self.size = 0
if default_setup:
self.setup() self._si = idaapi.string_info_t() def refresh(self, ea1=None, ea2=None):
"""Refreshes the strings list"""
if ea1 is None:
ea1 = idaapi.cvar.inf.minEA
if ea2 is None:
ea2 = idaapi.cvar.inf.maxEA idaapi.refresh_strlist(ea1, ea2)
self.size = idaapi.get_strlist_qty() def setup(self,
strtypes = STR_C,
minlen = 5,
only_7bit = True,
ignore_instructions = False,
ea1 = None,
ea2 = None,
display_only_existing_strings = False): if ea1 is None:
ea1 = idaapi.cvar.inf.minEA if ea2 is None:
ea2 = idaapi.cvar.inf.maxEA t = idaapi.strwinsetup_t()
t.strtypes = strtypes
t.minlen = minlen
t.only_7bit = only_7bit
t.ea1 = ea1
t.ea2 = ea2
t.display_only_existing_strings = display_only_existing_strings
idaapi.set_strlist_options(t) # Automatically refreshes
self.refresh() def _get_item(self, index):
if not idaapi.get_strlist_item(index, self._si):
return None
else:
return Strings.StringItem(self._si) def __iter__(self):
return (self._get_item(index) for index in xrange(0, self.size)) def __getitem__(self, index):
"""Returns a string item or None"""
if index >= self.size:
raise KeyError
else:
return self._get_item(index) # -----------------------------------------------------------------------
def GetIdbDir():
"""
Get IDB directory This function returns directory path of the current IDB database
"""
return os.path.dirname(idaapi.cvar.database_idb) + os.sep # -----------------------------------------------------------------------
def GetRegisterList():
"""Returns the register list"""
return idaapi.ph_get_regnames() # -----------------------------------------------------------------------
def GetInstructionList():
"""Returns the instruction list of the current processor module"""
return [i[0] for i in idaapi.ph_get_instruc() if i[0]] # -----------------------------------------------------------------------
def _Assemble(ea, line):
"""
Please refer to Assemble() - INTERNAL USE ONLY
"""
if type(line) == types.StringType:
lines = [line]
else:
lines = line
ret = []
for line in lines:
seg = idaapi.getseg(ea)
if not seg:
return (False, "No segment at ea")
ip = ea - (idaapi.ask_selector(seg.sel) << 4)
buf = idaapi.AssembleLine(ea, seg.sel, ip, seg.bitness, line)
if not buf:
return (False, "Assembler failed: " + line)
ea += len(buf)
ret.append(buf) if len(ret) == 1:
ret = ret[0]
return (True, ret) def Assemble(ea, line):
"""
Assembles one or more lines (does not display an message dialogs)
If line is a list then this function will attempt to assemble all the lines
This function will turn on batch mode temporarily so that no messages are displayed on the screen @param ea: start address
@return: (False, "Error message") or (True, asm_buf) or (True, [asm_buf1, asm_buf2, asm_buf3])
"""
old_batch = idc.Batch(1)
ret = _Assemble(ea, line)
idc.Batch(old_batch)
return ret def _copy_obj(src, dest, skip_list = None):
"""
Copy non private/non callable attributes from a class instance to another
@param src: Source class to copy from
@param dest: If it is a string then it designates the new class type that will be created and copied to.
Otherwise dest should be an instance of another class
@return: A new instance or "dest"
"""
if type(dest) == types.StringType:
# instantiate a new destination class of the specified type name?
dest = new.classobj(dest, (), {})
for x in dir(src):
# skip special and private fields
if x.startswith("__") and x.endswith("__"):
continue
# skip items in the skip list
if skip_list and x in skip_list:
continue
t = getattr(src, x)
# skip callable
if callable(t):
continue
setattr(dest, x, t)
return dest # -----------------------------------------------------------------------
class _reg_dtyp_t(object):
"""
INTERNAL
This class describes a register's number and dtyp.
The equal operator is overloaded so that two instances can be tested for equality
"""
def __init__(self, reg, dtyp):
self.reg = reg
self.dtyp = dtyp def __eq__(self, other):
return (self.reg == other.reg) and (self.dtyp == other.dtyp) # -----------------------------------------------------------------------
class _procregs(object):
"""Utility class allowing the users to identify registers in a decoded instruction"""
def __getattr__(self, attr):
ri = idaapi.reg_info_t()
if not idaapi.parse_reg_name(attr, ri):
raise AttributeError()
r = _reg_dtyp_t(ri.reg, ord(idaapi.get_dtyp_by_size(ri.size)))
self.__dict__[attr] = r
return r def __setattr__(self, attr, value):
raise AttributeError(attr) # -----------------------------------------------------------------------
class _cpu(object):
"Simple wrapper around GetRegValue/SetRegValue"
def __getattr__(self, name):
#print "cpu.get(%s)" % name
return idc.GetRegValue(name) def __setattr__(self, name, value):
#print "cpu.set(%s)" % name
return idc.SetRegValue(value, name) # --------------------------------------------------------------------------
class __process_ui_actions_helper(object):
def __init__(self, actions, flags = 0):
"""Expect a list or a string with a list of actions"""
if isinstance(actions, str):
lst = actions.split(";")
elif isinstance(actions, (list, tuple)):
lst = actions
else:
raise ValueError, "Must pass a string, list or a tuple" # Remember the action list and the flags
self.__action_list = lst
self.__flags = flags # Reset action index
self.__idx = 0 def __len__(self):
return len(self.__action_list) def __call__(self):
if self.__idx >= len(self.__action_list):
return False # Execute one action
idaapi.process_ui_action(
self.__action_list[self.__idx],
self.__flags) # Move to next action
self.__idx += 1 # Reschedule
return True # --------------------------------------------------------------------------
def ProcessUiActions(actions, flags=0):
"""
@param actions: A string containing a list of actions separated by semicolon, a list or a tuple
@param flags: flags to be passed to process_ui_action()
@return: Boolean. Returns False if the action list was empty or execute_ui_requests() failed.
""" # Instantiate a helper
helper = __process_ui_actions_helper(actions, flags)
return False if len(helper) < 1 else idaapi.execute_ui_requests((helper,)) # -----------------------------------------------------------------------
class peutils_t(object):
"""
PE utility class. Retrieves PE information from the database. Constants from pe.h
"""
PE_NODE = "$ PE header" # netnode name for PE header
PE_ALT_DBG_FPOS = idaapi.BADADDR & -1 # altval() -> translated fpos of debuginfo
PE_ALT_IMAGEBASE = idaapi.BADADDR & -2 # altval() -> loading address (usually pe.imagebase)
PE_ALT_PEHDR_OFF = idaapi.BADADDR & -3 # altval() -> offset of PE header
PE_ALT_NEFLAGS = idaapi.BADADDR & -4 # altval() -> neflags
PE_ALT_TDS_LOADED = idaapi.BADADDR & -5 # altval() -> tds already loaded(1) or invalid(-1)
PE_ALT_PSXDLL = idaapi.BADADDR & -6 # altval() -> if POSIX(x86) imports from PSXDLL netnode def __init__(self):
self.__penode = idaapi.netnode()
self.__penode.create(peutils_t.PE_NODE) imagebase = property(
lambda self: self.__penode.altval(peutils_t.PE_ALT_IMAGEBASE)
) header = property(
lambda self: self.__penode.altval(peutils_t.PE_ALT_PEHDR_OFF)
) def __str__(self):
return "peutils_t(imagebase=%s, header=%s)" % (hex(self.imagebase), hex(self.header)) def header(self):
"""
Returns the complete PE header as an instance of peheader_t (defined in the SDK).
"""
return self.__penode.valobj() # -----------------------------------------------------------------------
cpu = _cpu()
"""This is a special class instance used to access the registers as if they were attributes of this object.
For example to access the EAX register:
print "%x" % cpu.Eax
""" procregs = _procregs()
"""This object is used to access the processor registers. It is useful when decoding instructions and you want to see which instruction is which.
For example:
x = idautils.DecodeInstruction(here())
if x[0] == procregs.Esp:
print "This operand is the register ESP
"""

IDAPython类库---idautils.py的源码的更多相关文章

  1. IDAPython类库---idc.py的源码

    #!/usr/bin/env python #--------------------------------------------------------------------- # IDAPy ...

  2. IDAPython类库---idaapi.py的源码

    #ThisfilewasautomaticallygeneratedbySWIG(http://www.swig.org).#Version2.0.12##Donotmakechangestothis ...

  3. django python mange.py runserver 源码

    django python mange.py runserver 源码 入 口 mange.py文件 execute_from_command_line函数 输入参数为['manage.py', 'r ...

  4. quartz.net插件类库封装(含源码)

    1.前言 目录: 1.quartz.net任务调度:源码及使用文档 2.quartz.net插件类库封装 最近项目需要做一写任务作业调度的工作,最终选择了quartz.net这个插件,它提供了巨大的灵 ...

  5. python附录-re.py模块源码(含re官方文档链接)

    re模块 python官方文档链接:https://docs.python.org/zh-cn/3/library/re.html re模块源码 r"""Support ...

  6. 【第三方类库】underscore.js源码---each forEach 每次迭代跟{}比较的疑惑

    var each = _.each = _.forEach = function(obj, iterator, context) { if (obj == null) return; //首先判断是否 ...

  7. 史林枫:开源HtmlAgilityPack公共小类库封装 - 网页采集(爬虫)辅助解析利器【附源码+可视化工具推荐】

    做开发的,可能都做过信息采集相关的程序,史林枫也经常做一些数据采集或某些网站的业务办理自动化操作软件. 获取目标网页的信息很简单,使用网络编程,利用HttpWebResponse.HttpWebReq ...

  8. chrome源码编译常见的错误解决

    最近编译chrome浏览器源码时,下载源码和一般的设置,网络中都有说明,而且一般的说明都是类似的,然后都说编译成功了,但本人没有试成功,碰到常见的2个错误,记录下,不知道大家碰到没有. 1.pytho ...

  9. Python paramiko 修改源码实现用户命令抓取

    paramiko 源码修改 paramiko主要用来实现ssh客户端.服务端链接,上一节我们说到了堡垒机,堡垒机内有一个需求是“用户行为审计”,在这里我们就可以通过修改paramiko内文件的源码来实 ...

随机推荐

  1. 剑指 Offer 14- II. 剪绳子 II + 贪心 + 数论 + 快速幂

    剑指 Offer 14- II. 剪绳子 II 题目链接 因为有取模的操作,动态规划中max不能用了,我们观察:正整数从1开始,但是1不能拆分成两个正整数之和,所以不能当输入. 2只能拆成 1+1,所 ...

  2. CCF(地铁修建):向前星+dijikstra+求a到b所有路径中最长边中的最小值

    地铁修建 201703-4 这题就是最短路的一种变形,不是求两点之间的最短路,而是求所有路径中的最长边的最小值. 这里还是使用d数组,但是定义不同了,这里的d[i]就是表示从起点到i的路径中最长边中的 ...

  3. 【ZeyFraのJavaEE开发小知识03】@DateTimeFomat和@JsonFormat

    关于在Element UI的el-dialog组件中使用echarts的问题 问题描述: "Cannot read property 'getAttribute' of null" ...

  4. IT求职 非技术面试题汇总

    原文链接:https://blog.csdn.net/weixin_40845165/article/details/89852397 说明:原文是浏览网页时无意间看到的.扫了一眼,总结得还不错,感谢 ...

  5. FreeBSD 入门导言

    →→→→→导言: 导言,这一部分通常也被称作"前言"."导论"."概论"."楔子"."写在前面".& ...

  6. 超详细Linux新手快速入门(一)——Linux的介绍安装以及虚拟机的介绍安装

    一.Linux的介绍 1.Linux和Windows的比较  Linux是一款操作系统,其性能稳定,因其防火墙组件高效安全.简单易配置,所以获得了追求速度和安全的一些企业和人群的青睐.与我们日常所熟知 ...

  7. Pyqt5学习笔记(一)

    Python已有的GUI框架: Tkinter(python内嵌的GUI环境,使用TCL实现,易学易用,方便简单创GUI自带无需安装,适用于Unix.Windows和Mac系统组,在Tk8.0的后续版 ...

  8. Hibernate Validator异常HV000221解决办法

    自建博客地址:https://www.bytelife.net,欢迎访问! 本文为博客同步发表文章,为了更好的阅读体验,建议您移步至我的博客 本文作者: Jeffrey 本文链接: https://w ...

  9. reverseLinkedList(翻转链表)

    ReverseLinkedList(翻转链表) 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.非连续.非顺序指的是,通过指针把一组零散的内存块串 ...

  10. 微服务面试必问的Dubbo,这么详细还怕自己找不到工作?

    大家好,我是小羽. Dubbo 起源于阿里巴巴,对于我们做电商开发的人来说,基本是首选的技术,那么为何一个区区 soa 服务治理框架,会受到这么多人的青睐呢? 今天就跟着小羽一起看看这个微服务框架之一 ...