IDAFrida
IDA&Frida
前言
偶然间发现了一本秘籍《IDA脚本开发之旅》,这是白龙的系列文章,主要是安卓平台,笔者只是根据他的知识点学习,拓展,可以会稍微提及别的平台。本文并不会贴出他的思路分析,只对于源码进行学习,有兴趣可以加他星球一起学习!
另外本文也学习了无名侠的IDAFrida脚本,有兴趣可以加他星球一起学习!
脚本学习
IDA 静态分析 + Frida Hook,可以让分析变得更加丝滑。
这里我们会学习两个脚本 一个是白龙的 一个是 无名侠的。
ShowFridaCode
这是白龙写的一个Python脚本,用于生成Frida的Hook代码。主要包含了以下几个部分:
- 导入模块
- Frida Hook函数模板
- Frida Inline Hook函数模板
- 打印参数的函数
- 生成函数Hook代码的函数
- 生成Inline Hook代码的函数
- 根据地址生成Hook代码或Inline Hook代码的函数
- 生成初始化代码的函数
- 生成Dump内存代码的函数
- IDA View_Hooks类,用于处理在IDA视图中双击和单击事件
- 插件类,实现插件的初始化、运行和退出
前置知识
frida ida就不说了,主要说一下 其他的知识
android_dlopen_ext
是 Android 系统中的一个函数,用于在运行时动态加载共享库。与标准的 dlopen()
函数相比,android_dlopen_ext
提供了更多的参数选项和扩展功能,例如支持命名空间、符号版本等特性。该函数通常用于 Android 应用程序或系统进程中,用于加载共享库并获取其中定义的符号。在 Android 应用程序中,常常会使用 System.loadLibrary()
函数调用 android_dlopen_ext()
,从而加载与应用程序打包在一起的共享库文件。
在win上我们一般用 LoadLibrary
函数来加载 DLL(动态链接库)文件,并使用 GetProcAddress
函数来获取函数指针。在 iOS 平台上,可以使用 dlopen
函数来加载共享库,并使用 dlsym
函数来获取函数指针。
linker的调用流程:
- 在do_dlopen中通过find_library进行加载so
- 在加载完so后通过call_constructors完成init_array的加载
- find_library最后调用load_libray完成so的转载
- 最后通过load_library的elf_reader.load完成so的装载
了解了上面两个知识点,在Hook Native时,我们常常通过Spawn抢占两个较早的时机,1是 JNIOnLoad 前 2是 init_proc 以及 init_array 前
,就可以通过hook上述函数。
接下来就看源码学习吧、我会在代码中给出详细的注释
# Template 类提供了一种简单而安全的字符串替换机制,可以避免常见的安全漏洞,例如 SQL 注入攻击。在使用模板字符串时,可以将需要被替换的变量名以 $ 作为前缀,并在后面用花括号 {} 将变量名括起来。然后使用 substitute() 方法将变量值传递给模板,该方法会将变量名替换为对应的值。
from string import Template
# 文本行相关的函数和类。
import ida_lines
# IDA Pro 插件开发相关的函数和类。
import idaapi
import idc
# 该类型是插件开发的基本接口
from ida_idaapi import plugin_t# hook函数的模板
"""
这段代码通过调用 Module.findBaseAddress("$soName") 找到指定so库的基地址,然后使用 Interceptor.attach() 函数 hook 在基地址加上指定偏移量的函数。通过这些占位符,可以实现动态替换相应的值$soName 表示要 hook 的so库的名字;$functionName 表示要 hook 的函数名;$offset 表示要 hook 的函数在so库中的偏移量;$args 表示在 onEnter() 函数中输出的参数值;$result 表示在 onLeave() 函数中输出的返回值。
"""
hook_function_template = """
function hook_$functionName(){var base_addr = Module.findBaseAddress("$soName");Interceptor.attach(base_addr.add($offset), {onEnter(args) {console.log("call $functionName");$args},onLeave(retval) {$resultconsole.log("leave $functionName");}});
}
"""# 这段模板和上面那段差不多 主要用于某行指令去call函数的时候 在onEnter回调中 可以查看函数参数、寄存器值等调用上下文的详细信息。
inline_hook_template = """
function hook_$offset(){var base_addr = Module.findBaseAddress("$soName");Interceptor.attach(base_addr.add($offset), {onEnter(args) {console.log("call $offset");console.log(JSON.stringify(this.context));},});
}
"""# 参数打印
logTemplate = 'console.log("arg$index:"+args[$index]);\n'# 在指定共享库加载时执行自定义逻辑的功能,可以用于动态监视和修改目标进程中的行为。
dlopenAfter_template = """
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if(android_dlopen_ext != null){Interceptor.attach(android_dlopen_ext,{onEnter: function(args){var soName = args[0].readCString();if(soName.indexOf("$soName") !== -1){this.hook = true;}},onLeave: function(retval){if(this.hook) {this.hook = false;dlopentodo();}}});
}function dlopentodo(){//todo
}
"""# 在指定共享库初始化时执行自定义逻辑的功能 例如 init_proc 以及 init_array
init_template = """
function hookInit(){var linkername;var alreadyHook = false;var call_constructor_addr = null;var arch = Process.arch;if (arch.endsWith("arm")) {linkername = "linker";} else {linkername = "linker64";}var symbols = Module.enumerateSymbolsSync(linkername);for (var i = 0; i < symbols.length; i++) {var symbol = symbols[i];if (symbol.name.indexOf("call_constructor") !== -1) {call_constructor_addr = symbol.address;}}if (call_constructor_addr.compare(NULL) > 0) {console.log("get construct address");Interceptor.attach(call_constructor_addr, {onEnter: function (args) {if(alreadyHook === false){const targetModule = Process.findModuleByName("$soName");if (targetModule !== null) {alreadyHook = true;inittodo();}}}});}
}function inittodo(){//todo
}
"""# 以十六进制形式打印指定so文件中指定偏移量处内存数据
dump_template = """
function dump_$offset() {var base_addr = Module.findBaseAddress("$soName");var dump_addr = base_addr.add($offset);console.log(hexdump(dump_addr, {length: $length}));
}
"""# 生成参数打印脚本
def generate_printArgs(argNum):if argNum == 0:return "// no args"else:temp = Template(logTemplate)logText = ""for i in range(argNum):logText += temp.substitute({'index': i})logText += " "return logText# 生成hook函数模板 通过 soName, functionName, address, argNum, hasReturn
def generate_for_func(soName, functionName, address, argNum, hasReturn):# 根据参数个数打印argsPrint = generate_printArgs(argNum)# 根据是否有返回值判断是否打印retvalretPrint = "// no return"if hasReturn:retPrint = "console.log(retval);"# 使用Python提供的Template字符串模板方法temp = Template(hook_function_template)offset = getOffset(address)result = temp.substitute({'soName': soName, "functionName": functionName, "offset": hex(offset), "args": argsPrint, "result": retPrint})print(result)# 获取地址偏移
def getOffset(address):if idaapi.get_inf_structure().is_64bit():return addresselse:return address + idc.get_sreg(address, "T")# 生成inline hook函数模板 通过 soName, address
def generate_for_inline(soName, address):offset = getOffset(address)temp = Template(inline_hook_template)result = temp.substitute({'soName': soName, "offset": hex(offset)})if idaapi.is_call_insn(address):callAddr = idaapi.get_name_ea(0, idc.print_operand(address, 0))if callAddr != idaapi.BADADDR:callAddress = idc.get_operand_value(address, 0)argnum, _ = get_argnum_and_ret(callAddress)argsPrint = generate_printArgs(argnum)print(result.replace("console.log(JSON.stringify(this.context));", argsPrint))else:print(result)else:print(result)# 这个函数的作用是获取指定地址处的函数的参数个数和返回值类型。
def get_argnum_and_ret(address):# 获取反编译的代码cfun = idaapi.decompile(address)# 获取参数长度argnum = len(cfun.arguments)ret = True# 先调用cfun.print_dcl() 函数获取函数的声明语句 在调用 tag_remove 去掉字符串中的标记,它可以将类似 <hex number>、<function name> 这样的标记从字符串中去掉dcl = ida_lines.tag_remove(cfun.print_dcl())# 判断函数声明语句是否有返回值 需要注意:当一个函数返回 "void *" 类型时,它实际上是返回一个指针,指向某个数据的内存地址。if (dcl.startswith("void ") is True) & (dcl.startswith("void *") is False):ret = Falsereturn argnum, ret# 通过地址去生成 hook 脚本
def generate_for_func_by_address(addr):soName = idaapi.get_root_filename()functionName = idaapi.get_func_name(addr)argnum, ret = get_argnum_and_ret(addr)generate_for_func(soName, functionName, addr, argnum, ret)# 通过地址去生成 inline hook 脚本
def generate_for_inline_by_address(addr):soName = idaapi.get_root_filename()generate_for_inline(soName, addr)# 如果传入的地址等于函数头的地址 那么就hook函数头 否则生成函数中
def generate_snippet(addr):startAddress = idc.get_func_attr(addr, idc.FUNCATTR_START)if startAddress == addr:generate_for_func_by_address(addr)elif startAddress == idc.BADADDR:print("不在函数内")else:generate_for_inline_by_address(addr)# 生成初始化模板 脚本
def generateInitCode():soName = idaapi.get_root_filename()print(Template(dlopenAfter_template).substitute({'soName': soName}))print(Template(init_template).substitute({'soName': soName}))# 生成dump 脚本
def generate_dump_script(start, length):soName = idaapi.get_root_filename()print(Template(dump_template).substitute({'soName': soName, "offset": hex(start), "length": hex(length)}))# IDA界面hook
class Hook(idaapi.View_Hooks):# 监听双击 如果是反汇编视图,则获取当前光标所在地址 然后生成模板def view_dblclick(self, view, event):widgetType = idaapi.get_widget_type(view)if widgetType == idaapi.BWN_DISASM:global initializedif not initialized:initialized = TruegenerateInitCode()address = idaapi.get_screen_ea()generate_snippet(address)# 监听单击事件 获取开始和结束地址 进行dumpdef view_click(self, view, event):widgetType = idaapi.get_widget_type(view)if widgetType == idaapi.BWN_DISASM:start = idc.read_selection_start()end = idc.read_selection_end()if (start != idaapi.BADADDR) and (end != idaapi.BADADDR):length = end - startgenerate_dump_script(start, length)class GenFrida_Plugin_t(plugin_t):# 关于插件的注释# 当鼠标浮于菜单插件上方时,IDA左下角所示comment = "A Toy Plugin for Generating Frida Code"# 帮助信息,我们选择不填help = "unknown"# 插件的特性,是一直在内存里,还是运行一下就退出,等等flags = idaapi.PLUGIN_KEEP# 插件的名字wanted_name = "ShowFridaCode"# 快捷键,我们选择置空不弄wanted_hotkey = ""# 插件刚被加载到IDA内存中# 这里适合做插件的初始化工作def init(self):print("ShowFridaCode init")return idaapi.PLUGIN_KEEP# 插件运行中# 这里是主要逻辑def run(self, arg):print("ShowFridaCode run")global myViewHookmyViewHook = Hook()myViewHook.hook()# 插件卸载退出的时机# 这里适合做资源释放def term(self):passinitialized = False
# register IDA plugindef PLUGIN_ENTRY():return GenFrida_Plugin_t()
ShowFridaCode 这是一个十分简单但是很优秀的脚本 包含了ida界面hook frida语法 模板生成 安卓初始化流程 arm汇编的理解
IDAFrida
这是无名侠写的一个Python脚本。这段脚本主要是一个基于 IDA Pro 和 Frida 的集成工具,用于辅助逆向工程师进行动态调试。具体来说,这个工具通过 IDA Pro 分析程序的代码,提取出函数的信息(如函数名、地址、参数数量等),然后使用 Frida 注入 JS 脚本,在运行时拦截这些函数,并在函数进入和返回时打印出函数的参数和返回值。这个工具的具体功能包括:
- 通过 IDA Pro 分析程序代码,提取函数信息;
- 使用 Frida 注入 JS 脚本,拦截函数并打印参数和返回值;
- 提供一个 UI 界面,让用户可以修改 Frida 命令和 JS 脚本。
具体来说,这个脚本实现了以下几个类和函数:
ActionManager
类:一个动作管理器,用于管理 IDA Pro 中的动作。它提供了注册、初始化和销毁动作的功能;Action
类:一个动作基类,用于创建 IDA Pro 中的动作。它包含了动作的名称、描述、快捷键等信息,并提供了动作激活和更新的虚函数;Configuration
类:一个配置类,用于保存和读取用户配置信息。它包含了 Frida 命令和 JS 脚本的配置信息,并提供了保存和读取配置信息的方法;ConfigurationUI
类:一个 UI 类,用于提供一个 UI 界面,让用户可以修改配置信息;ScriptGenerator
类:一个脚本生成器类,用于生成注入到目标程序中的 JS 脚本。
我们直接看源码学习吧 同样我也会给出详细的注释
import ida_name
import idaapi###################
# from: https://github.com/igogo-x86/HexRaysPyTools
# 这是一个名为 ActionManager 的类,它用于管理多个动作(Action)并提供初始化和清理方法
class ActionManager(object):def __init__(self):self.__actions = []def register(self, action):self.__actions.append(action)idaapi.register_action(# 创建一个动作描述符(action descriptor),以便注册动作并将其添加到IDA的菜单或工具栏中。action.name是动作的名称,action.description是动作的描述,action是动作的处理程序,action.hotkey是动作的快捷键。idaapi.action_desc_t(action.name, action.description, action, action.hotkey))def initialize(self):passdef finalize(self):for action in self.__actions:idaapi.unregister_action(action.name)# 可以通过 register函数注册动作
action_manager = ActionManager()#这是一个基类。它继承自 idaapi.action_handler_t 类,并定义了 activate 和 update 方法作为接口,但是这些方法都是抛出了 NotImplementedError 异常,需要在子类中进行实现。此外,该类还定义了 name 属性,用于返回一个标识名称,方便在 IDA 中进行注册和调用
class Action(idaapi.action_handler_t):"""Convenience wrapper with name property allowing to be registered in IDA using ActionManager"""description = Nonehotkey = Nonedef __init__(self):super(Action, self).__init__()@propertydef name(self):return "FridaIDA:" + type(self).__name__# 当从菜单、弹出菜单、工具栏或以编程方式触发操作时,会调用它def activate(self, ctx):# type: (idaapi.action_activation_ctx_t) -> Noneraise NotImplementedError# 当UI的上下文发生更改时会调用此函数def update(self, ctx):# type: (idaapi.action_activation_ctx_t) -> Noneraise NotImplementedError############################################################################
import ida_funcs
import idc
import json
import osfrom PyQt5 import QtCore
from PyQt5.Qt import QApplication
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QTextEdit# [offset] => offset of target function in hex value format.
# [funcname] => function name
# [filename] => input file name of IDA. e.g. xxx.so / xxx.exe
default_template = """
(function () {// @ts-ignorefunction print_arg(addr) {try {var module = Process.findRangeByAddress(addr);if (module != null) return "\\n"+hexdump(addr) + "\\n";return ptr(addr) + "\\n";} catch (e) {return addr + "\\n";}}// @ts-ignorefunction hook_native_addr(funcPtr, paramsNum) {var module = Process.findModuleByAddress(funcPtr);try {Interceptor.attach(funcPtr, {onEnter: function (args) {this.logs = "";this.params = [];// @ts-ignorethis.logs=this.logs.concat("So: " + module.name + " Method: [funcname] offset: " + ptr(funcPtr).sub(module.base) + "\\n");for (let i = 0; i < paramsNum; i++) {this.params.push(args[i]);this.logs=this.logs.concat("this.args" + i + " onEnter: " + print_arg(args[i]));}}, onLeave: function (retval) {for (let i = 0; i < paramsNum; i++) {this.logs=this.logs.concat("this.args" + i + " onLeave: " + print_arg(this.params[i]));}this.logs=this.logs.concat("retval onLeave: " + print_arg(retval) + "\\n");console.log(this.logs);}});} catch (e) {console.log(e);}}// @ts-ignorehook_native_addr(Module.findBaseAddress("[filename]").add([offset]), [nargs]);
})();
"""# 配置类: 设置和存储配置信息的方法
class Configuration:def __init__(self) -> None:self.frida_cmd = """frida -U --attach-name="com.example.app" -l gen.js --no-pause"""self.template = default_templateif os.path.exists("IDAFrida.json"):self.load()def set_frida_cmd(self, s):self.frida_cmd = sself.store()def set_template(self, s):self.template = sself.store()def reset(self):self.__init__()def store(self):try:data = {"frida_cmd": self.frida_cmd, "template": self.template}open("IDAFrida.json", "w").write(json.dumps(data))except Exception as e:print(e)def load(self):try:data = json.loads(open("IDAFrida.json", "r").read())self.frida_cmd = data["frida_cmd"]self.template = data["template"]except Exception as e:print(e)global_config = Configuration()# 修改配置UI 界面类
class ConfigurationUI(QDialog):def __init__(self, conf: Configuration) -> None:super(ConfigurationUI, self).__init__()self.conf = confself.edit_template = QTextEdit()self.edit_template.setPlainText(self.conf.template)layout = QHBoxLayout()layout.addWidget(self.edit_template)self.setLayout(layout)def closeEvent(self, a0) -> None:self.conf.set_template(self.edit_template.toPlainText())self.conf.store()return super().closeEvent(a0)# 脚本生成类
class ScriptGenerator:def __init__(self, configuration: Configuration) -> None:self.conf = configurationself.imagebase = idaapi.get_imagebase()@staticmethoddef get_idb_filename():return os.path.basename(idaapi.get_input_file_path())@staticmethoddef get_idb_path():return os.path.dirname(idaapi.get_input_file_path())# 根据地址获取函数的名字def get_function_name(self,ea): # https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml"""Get the real function name"""# Try to demanglefunction_name = idc.demangle_name(idc.get_func_name(ea), idc.get_inf_attr(idc.INF_SHORT_DN))# if function_name:# function_name = function_name.split("(")[0]# Function name is not mangledif not function_name:function_name = idc.get_func_name(ea)if not function_name:function_name = idc.get_name(ea, ida_name.GN_VISIBLE)# If we still have no function name, make one up. Format is - 'UNKN_FNC_4120000'if not function_name:function_name = "UNKN_FNC_%s" % hex(ea)return function_name#替换模板中某些字符串def generate_stub(self, repdata: dict):s = self.conf.templatefor key, v in repdata.items():s = s.replace("[%s]" % key, v)return s# 根据地址列表来生成模板def generate_for_funcs(self, func_addr_list) -> str:stubs = []for func_addr in func_addr_list:dec_func = idaapi.decompile(func_addr)repdata = {"filename": self.get_idb_filename(),"funcname": self.get_function_name(func_addr),"offset": hex(func_addr - self.imagebase),"nargs": hex(dec_func.type.get_nargs())}stubs.append(self.generate_stub(repdata))return "\n".join(stubs)# 把生成frida脚本 保存到文件 并且置剪辑版def generate_for_funcs_to_file(self, func_addr_list, filename) -> bool:data = self.generate_for_funcs(func_addr_list)try:open(filename, "w").write(data)print("The generated Frida script has been exported to the file: ", filename)except Exception as e:print(e)return Falsetry:QApplication.clipboard().setText(data)print("The generated Frida script has been copied to the clipboard!")except Exception as e:print(e)return Falsereturn Trueclass Frida:def __init__(self, conf: Configuration) -> None:self.conf = conf# 菜单动作类 该方法首先判断当前窗口是否是函数列表、伪代码或反汇编窗口(通过检查 ctx.form_type 属性),如果是,则将当前菜单项关联到右键弹出菜单,并返回 AST_ENABLE_FOR_WIDGET;否则返回 AST_DISABLE_FOR_WIDGET。 如果菜单项与右键弹出菜单关联成功,用户就可以在相应的窗口右键单击并选择该菜单项来触发菜单操作。在 activate 方法中可以实现具体的菜单操作逻辑。
class IDAFridaMenuAction(Action):TopDescription = "IDAFrida"def __init__(self):super(IDAFridaMenuAction, self).__init__()def activate(self, ctx) -> None:raise NotImplementeddef update(self, ctx) -> None:if ctx.form_type == idaapi.BWN_FUNCS or ctx.form_type==idaapi.BWN_PSEUDOCODE or ctx.form_type==idaapi.BWN_DISASM:idaapi.attach_action_to_popup(ctx.widget, None, self.name, self.TopDescription + "/")return idaapi.AST_ENABLE_FOR_WIDGETreturn idaapi.AST_DISABLE_FOR_WIDGET# 继承菜单动作类
class GenerateFridaHookScript(IDAFridaMenuAction):description = "Generate Frida Script"def __init__(self):super(GenerateFridaHookScript, self).__init__()#在activate方法中,使用ScriptGenerator类生成Frida脚本,并将其写入到文件中。如果当前活动窗口类型是函数窗口(BWN_FUNCS),则选择被选中的函数,否则只选择当前屏幕所在函数作为目标。最后调用gen.generate_for_funcs_to_file方法生成脚本并写入到指定的文件中def activate(self, ctx):gen = ScriptGenerator(global_config)idb_path = os.path.dirname(idaapi.get_input_file_path())out_file = os.path.join(idb_path, "IDAhook.js")if ctx.form_type==idaapi.BWN_FUNCS:selected = [idaapi.getn_func(idx).start_ea for idx in ctx.chooser_selection] #from "idaapi.getn_func(idx - 1)" to "idaapi.getn_func(idx)"else:selected=[idaapi.get_func(idaapi.get_screen_ea()).start_ea]gen.generate_for_funcs_to_file(selected, out_file)class ViewFridaTemplate(IDAFridaMenuAction):description = "View Frida Template"def __init__(self):super(ViewFridaTemplate, self).__init__()def activate(self, ctx):ui = ConfigurationUI(global_config)ui.show()ui.exec_()class RunGeneratedScript(IDAFridaMenuAction):description = "Run Generated Script"def __init__(self):super(RunGeneratedScript, self).__init__()def activate(self, ctx):print("template")# 设置执行命令
class SetFridaRunCommand(IDAFridaMenuAction):description = "Set Frida Command"def __init__(self):super(SetFridaRunCommand, self).__init__()def activate(self, ctx):print("template")action_manager.register(GenerateFridaHookScript())
# action_manager.register(RunGeneratedScript())
action_manager.register(ViewFridaTemplate())
# action_manager.register(SetFridaRunCommand())
总的来说,这个脚本实现了一个较为完整的 IDA Pro 和 Frida 集成工具,并提供了一些 UI 功能和便捷的函数接口,使得逆向工程师能够更加高效地进行动态调试和分析。
MyFridaPlugin
通过上面两个例子,我们已经学习了frida模板的生成 以及如何和ida的ui交互 这样我们就可以自己写个插件 整合上面的代码 并且可以添加自己想要的功能
源码: https://github.com/AnxiangLemon/MyIdaFrida
相关文章:
IDAFrida
IDA&Frida 前言 偶然间发现了一本秘籍《IDA脚本开发之旅》,这是白龙的系列文章,主要是安卓平台,笔者只是根据他的知识点学习,拓展,可以会稍微提及别的平台。本文并不会贴出他的思路分析,只对于源码进…...
通过百度文心一言大模型作画尝鲜,感受国产ChatGPT的“狂飙”
3月16日下午,百度于北京总部召开新闻发布会,主题围绕新一代大语言模型、生成式AI产品文心一言。百度创始人、董事长兼首席执行官李彦宏,百度首席技术官王海峰出席,并展示了文心一言在文学创作、商业文案创作、数理推算、中文理解、…...
Nacos 注册中心 - 健康检查机制源码
目录 1. 健康检查介绍 2. 客户端健康检查 2.1 临时实例的健康检查 2.2 永久实例的健康检查 3. 服务端健康检查 3.1 临时实例的健康检查 3.2 永久实例服务端健康检查 1. 健康检查介绍 当一个服务实例注册到 Nacos 中后,其他服务就可以从 Nacos 中查询出该服务…...
Transformer在计算机视觉中的应用-VIT、TNT模型
上期介绍了Transformer的结构、特点和作用等方面的知识,回头看下来这一模型并不难,依旧是传统机器翻译模型中常见的seq2seq网络,里面加入了注意力机制,QKV矩阵的运算使得计算并行。 当然,最大的重点不是矩阵运算&…...
快速入门Zookeeper技术.黑马教程
快速入门Zookeeper技术.黑马教程一、初识 Zookeeper二、ZooKeeper 安装与配置三、ZooKeeper 命令操作1.Zookeeper 数据模型2.Zookeeper 服务端常用命令3.Zookeeper 客户端常用命令四、ZooKeeper JavaAPI 操作五、ZooKeeper JavaAPI 操作1.Curator 介绍2.Curator API 常用操作2.…...
网易C++实习一面
说下C11新特性 auto有没有效率上的问题?为什么?发生在什么时候? 说下单例模式 什么时候需要加锁,什么时候不需要加锁? 像printf这样的函数,自己本身不修改数据,但是其他人会修改数据&#x…...
进程和线程的区别和联系
进程和线程的区别和联系1. 认识线程2. 进程和线程的关系3. 进程和线程的区别4. 线程共享了进程哪些资源1. 上下文切换2. 线程共享了进程哪些资源1.代码区2. 数据区3. 堆区1. 认识线程 线程是进程的一个实体,它被包含在进程中,一个进程至少包含一个线程,一个进程也可以包含多个…...
Java学习笔记——集合
目录集合与数组的对比集合体系结构Collection——常见成员方法Collection——迭代器基本使用Collection——迭代器原理分析Collection——迭代器删除方法增强for——基本格式增强for——注意点Collection——练习集合与数组的对比 package top.xxxx.www.CollectionDemo;import …...
差分运放公式推导-运算放大器
不知道大家有没遇到这种情况,在计算电路的时候,有时候会突然的忘记一些公式啊啥的,需要回去翻看笔记或者查资料,知其然而不知其所以然。今天跟大家一起来一起推导一遍差分运放的计算过程。 计算过程其实归根结底还是根据运放的虚…...
金丹二层 —— 字符串长度求解的四种方法
前言: 1.CSDN由于我的排版不怎么好看,我的有道云笔记比较美观,请移步有道云笔记 2.修炼必备 1)入门必备:VS2019社区版,下载地址:Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (m…...
深入剖析Linux——进程信号
致前行的人: 要努力,但不着急,繁花锦簇,硕果累累都需要过程! 目录 1.信号概念 1.1生活角度的信号 2. 技术应用角度的信号 3.Linux操作系统中查看信号 4.常用信号发送 4.1通过键盘发送信号 4.2调用系统函数发送信号 4.3…...
API-Server的监听器Controller的List分页失效
前言 最近做项目,还是K8S的插件监听器(理论上插件都是通过API-server通信),官方的不同写法居然都能出现争议,争议点就是对API-Server的请求的耗时,说是会影响API-Server。实际上通过源码分析两着有差别&am…...
jupyter notebook 进阶使用:nbextensions,终极避坑
jupyter notebook 进阶使用:nbextensions,终极避坑吐槽安装 jupyter_contrib_nbextensions1. Install the python package(安装python包)方法一,PIP:方法二,Conda(推荐)&…...
C 语言编程 — Doxygen + Graphviz 静态项目分析
目录 文章目录目录安装配置解析Project related configuration optionsBuild related configuration optionsConfiguration options related to warning and progress messagesConfiguration options related to the input filesConfiguration options related to source brows…...
Mybatis报BindingException:Invalid bound statement (not found)异常
一、前言 本文的mybatis是与springboot整合时出现的异常,若使用的不是基于springboot,解决思路也大体一样的。 二、从整合mybatis的三个步骤排查问题 但在这之前,我们先要知道整合mybatis的三个重要的工作,如此才能排查&#x…...
HttpRunner3.x(1)-框架介绍
HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。主要特征继承的所有强大功能requests ,只需以人工方式获得乐趣即可处理HTTP…...
pytest学习和使用20-pytes如何进行分布式测试?(pytest-xdist)
20-pytes如何进行分布式测试?(pytest-xdist)1 什么是分布式测试?2 为什么要进行分布式测试?2.1 场景1:自动化测试场景2.2 场景2:性能测试场景3 分布式测试有什么特点?4 分布式测试关…...
三、Python 操作 MongoDB ----非 ODM
文章目录一、连接器的安装和配置二、新增文档三、查询文档四、更新文档五、删除文档一、连接器的安装和配置 pymongo: MongoDB 官方提供的 Python 工具包。官方文档: https://pymongo.readthedocs.io/en/stable/ pip安装,命令如下࿱…...
求最大公约数和最小公倍数---辗转相除法(欧几里得算法)
目录 一.GCD和LCM 1.最大公约数 2.最小公倍数 二.暴力求解 1.最大公约数 2.最小公倍数 三.辗转相除法 1.最大公约数 2.最小公倍数 一.GCD和LCM 1.最大公约数 最大公约数(Greatest Common Divisor,简称GCD)指的是两个或多个整数共有…...
音视频开发_获取媒体文件的详细信息
一、前言 做音视频开发过程中,经常需要获取媒体文件的详细信息。 比如:获取视频文件的总时间、帧率、尺寸、码率等等信息。 获取音频文件的的总时间、帧率、码率,声道等信息。 这篇文章贴出2个我封装好的函数,直接调用就能获取媒体信息返回,copy过去就能使用,非常方便。…...
Springboot集成Swagger
一、Swagger简介注意点! 在正式发布的时候要关闭swagger(出于安全考虑,而且节省内存空间)之前开发的时候,前端只用管理静态页面, http请求到后端, 模板引擎JSP,故后端是主力如今是前…...
Vue全新一代状态管理库 Pinia【一篇通】
文章目录前言1. Pinia 是什么?1.1 为什么取名叫 Pinia?1.2. 为什么要使用 Pinia ?2. 安装 Pinia2.1.创建 Store2.1.1. Option 类型 Store2.1.2 Setup 函数类型 Store2.1.3 模板中使用3. State 的使用事项(Option Store )3.1 读取 State3.2 …...
STM32 -4 关于STM32的RAM、ROM
一 stm32 的flash是什么、有什么用、注意事项、如何查看 一 、说明 它主要用于存储代码,FLASH 存储器的内容在掉电后不会丢失,STM32 芯片在运行的时候,也能对自身的内部 FLASH 进行读写,因此,若内部 FLASH 存储了应用…...
第一个 Qt 程序
第一个 Qt 程序 “hello world ”的起源要追溯到 1972 年,贝尔实验室著名研究员 Brian Kernighan 在撰写 “B 语言教程与指导(Tutorial Introduction to the Language B)”时初次使用(程序),这是目前已 知最早的在计算机著作中将…...
Spring注解驱动开发--AOP底层原理
Spring注解驱动开发–AOP底层原理 21. AOP-AOP功能测试 AOP:【动态代理】 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式; 1、导入aop模块:Spring AOP,(Spring-aspects) 2、定义一个业务逻辑类(Ma…...
对象的动态创建和销毁以及对象的复制,赋值
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C 🔥座右铭:“不要等到什么都没有了,才…...
JVM调优,调的是什么?目的是什么?
文章目录前言一、jvm是如何运行代码的?二、jvm的内存模型1 整体内存模型结构图2 堆中的年代区域划分3 对象在内存模型中是如何流转的?4 什么是FULL GC,STW? 为什么会发生FULL GC?5 要调优,首先要知道有哪些垃圾收集器及哪些算法6 调优不是盲目的,要有依据,几款内…...
docker部署zabbix监控
docker部署zabbix监控 1、环境说明 公有云ubuntu22.04 系统->部署docker环境zabbix-server 6.4 2、准备docker环境 更新apt以及安装一些必要的系统工具 sudo apt-get update sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-co…...
C语言刷题(6)(猜名次)——“C”
各位CSDN的uu们你们好呀,今天,小雅兰还是在复习噢,今天来给大家介绍一个有意思的题目 题目名称: 猜名次 题目内容: 5位运动员参加了10米台跳水比赛,有人让他们预测比赛结果: A选…...
两年外包生涯,感觉自己废了一半....
先说一下自己的情况。大专生,17年通过校招进入湖南某软件公司,干了接近2年的点点点,今年年上旬,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了五年的功能测试…...
东莞建设网站公司简介/青岛百度seo代理
看评论区大佬都水过了,我也先水一下,O(mn)O(mn)O(mn)复杂度都可以过,可以见得数据之水当然这道题的数据量本身也是符合这个复杂度的, 暴力解法 ps:fill和memset别用得太多,有时候会被卡死 //运行时间&am…...
网站建设终身不用维护/直接进网站的浏览器
1、段的定义及类型 Oracle中的段(segment)是占用磁盘空间的一个对象,最常见的段类型包括: l 聚簇cluster l 表table l 表分区 tablepartition l 索引 index l 索引分区 l Lob分区lob partition、lob子分区lobsubpartition、lob索引lobindex、lob段…...
做快递单的网站会不会是骗人的/永久免费的电销外呼系统
基于PHP的房屋出租管理系统一 项目介绍 此房屋管理系统基于php开发,数据库mysql,采用现代化UI,界面友好,用于快速生成每月租金水电,同时可以快速生成收据单,方便使用。 技术栈 phpmysqljquery.jsphpstudy …...
中山低价网站建设/竞价推广哪家公司好
环境 elasticsearch版本:7.13.2 实例 默认情况下当用elasticsearch进行深度分页查询时的size-from大于10000的时候,就会报错。 可以通过设置settings的index.max_result_window属性来解决。 PUT /users/_settings {"index.max_result_window&qu…...
网站制作加教程视频/中国万网官网
java的3大加载器 引导类加载器 BootStrapClassLoader 扩展类加载器 ExtensionClassLoader 应用程序类加载器 ApplicationClassLoader 三大类加载器可以简单的理解为: BootStrapClassLoader加载的是JVM核心类的类库 ExtensionClassLoader加载的是扩展类的类库 Appl…...
wordpress 添加phpmyadmin/搜索引擎优化seo公司
相比于Ubuntu 11.04的默认软件,Ubuntu 11.10会进行一些改变。但是这一些改变仍然在讨论之中。首先是桌面环境,相比Ubuntu 11.04的UnityGnome2.x,Ubuntu 11.10会让大家更期待,更完善的Unity官方源的Gnome3,当然前提是在11月前Ubunt…...