郁金香2021年游戏辅助技术(初级班)(中)

news2024/12/24 3:31:32

郁金香2021年游戏辅助技术初级班(中)

    • MFC动态链接库与注入DLL
    • 在目标进程分配内存写入代码
    • 向目标进程注入代码加载DLL
    • 029-分析角色对象的属性
      • 外平栈的call计算参数数量
    • C,C++编写代码读取对象属性值
    • C,C++输入输出重定向
    • C,C++定时器与主线程
      • 定时器(微软文档)
      • 代码实例
    • 基址偏移分析、角色信息复习

MFC动态链接库与注入DLL

之前我们都是用远程线程的方式调用CALL,今天我们通过动态链接库以本地的方式调用CALL。

在这里插入图片描述

任何的动态链接库都是无法启动的程序,不能够单独运行,它只能依附于其他exe程序,动态链接库都是为其他的exe的独立的进程服务的,它相当于代码库的一个资源。
我们就是要通过这个动态链接库里面带一个窗口,然后给它注入到另外一个进程里面去,显示出窗口,并测试里面的CALL。

在这里插入图片描述
双击资源文件目录中的*.rc文件,就会切换到资源视图,并在资源视图中的名称为*.rc目录上鼠标右击弹出菜单,点击添加资源、选择Dialog选项,即可为我们动态链接库添加一个对话框资源:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

为了能够显示出该窗口,我们首先要为该对话框窗口资源添加类:

在这里插入图片描述

在这里插入图片描述

显示窗口:

在这里插入图片描述

在这里插入图片描述

注意,编译程序之前记得把平台换成x86,因为那个测试用的代码注入器只能在x86平台下使用。

在这里插入图片描述

我们现在就要通过代码注入器把生成的.dll文件注入到正在运行的.exe进程里面去:

在这里插入图片描述

在这里插入图片描述

我们这个窗口注入进去后,就可以去调用call00、call01、call02,这个时候调用就非常方便了。

我们给窗口添加一个按钮,并在按钮的单击事件里添加调用call02这个CALL的代码:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


在目标进程分配内存写入代码

写代码(控制台应用程序)把call02函数代码写入到目标进程里面去。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 参数flAllocationType
    其中的MEM_OMMIT是说用这种方式分配的内存可以被置换到我们硬盘上,也就是我们所说的页面内存。

在这里插入图片描述

  • 参数flProtect
    这个是我们内存的页面属性,其中的PAGE_EXECUTE_READWRITE就是说,分配的这块内存可读、可写、可执行。

在这里插入图片描述

在这里插入图片描述

我们可以看到在目标进程中分配的内存是从0x00250000开始的,我们用x64dbg来验证看看有没有这块内存:

在这里插入图片描述

这个时候能够看到,的确分配了一大块为0的内存空间。

接下来就要把我们的数据(代码)写到在目标进程里面分配的这块内存空间,怎么写呢?
先写一段裸汇编:

在这里插入图片描述

再把这段裸汇编写入到目标进程地址空间里面分配的内存空间:
在这里插入图片描述

在这里插入图片描述


向目标进程注入代码加载DLL

目标::
写代码(控制台应用程序)把用MFC写的DLL(26课的DLL的全路径)注入(写入)到目标进程(23课)里面去(通过CreateRemoteThread创建远程线程,调用LoadLibrary加载该DLL)。

- VirtualAllocEx
- CreateRemoteThread
- LoadLibraryA	 

LoadLibrary有两个作用:
1、加载DLL到当前进程
2、获取模块的基址(模块句柄)
有时候我们会碰到一些动态基址的游戏,那么我们就需要用到这个函数LoadLibrary来动态的定位它那个主模块的基址。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这就是我们要注入的DLL的全路径,我们写进来了。

在这里插入图片描述


029-分析角色对象的属性

在这里插入图片描述


外平栈的call计算参数数量

在这里插入图片描述

要进到该call内部,查看[ebp+8]是第一个参数,[ebp+C]是第二个参数,[ebp+10]是第三个参数。
上图最上方有push esi和push edi,是用来保存non-volatile非易失性寄存器的,后面有对应的pop edi和pop esi:

在这里插入图片描述

在这里插入图片描述

所以edi和esi不是附近84E0E0这个call的参数,这个call只有3个参数。


C,C++编写代码读取对象属性值

因为每个进程都有一个控制台,所以我们为了方便测试,可以通过AllocConsole这个API打开控制台,方便输出。
如果测试当中,控制台显示不正常(不出结果),可以先通过FreeConsole0这个API把它关闭,再重新打开试试看。

我们怎么来挂接这个主线程呢?
挂接主线程这是一个比较复杂的过程,方法有很多,现在我们能够快速用到的,可以用定时器的方法把它附加到游戏窗口所在的线程,因为游戏这个窗口界面就是运行在主线程的,所以我们可以用这种方法来挂接游戏主线程(后面的课还会讲其他挂接主线程的方法)。

我们先通过spy++取得游戏窗口的标题和类名:

在这里插入图片描述

如果这个游戏窗口句柄找到了,通过SetTimer这个方式我们就能够把这个主线代码给附加进去,当然以前我们用的是子类化(用SetWindowLong来进行子类化),或者是SetWindowsHookEx来挂消息钩子,还可以做一个内联的inline hook也可以,这些都可以挂到主线程,这里我们用简单的方式来挂主线程。

有了游戏窗口的标题和类名,我们就可以获得它的窗口句柄,然后安装定时器:

在这里插入图片描述

上图SetTimer的第2个参数是随便一个标识该定时器的ID值,第3个参数是每隔多长时间(毫秒级别)执行第4个参数所表示的函数,第4个参数这个函数有固定的一个格式,它实际上是定时器的一个回调:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

主线代码中的KillTimer函数的执行,可以把安装的定时器1236给关闭掉,这样就可以实现每点击一次按钮(安装1236定时器)只执行一次(关闭定时器,同时执行主线代码)。

在这里插入图片描述

在这里插入图片描述

经测试我们发现没有返回值,我们修改一下代码,看看输出的当前线程ID是否是主线程,看它是否挂到了游戏的主线程上面,并通过添加MessageBeep(1);这行代码,可以在x64dbg中方便定位到MessageBeep函数下面的这个CALL,以便调试:

在这里插入图片描述

在这里插入图片描述

从上图我们可以发现主线程是2CE4,说明挂上游戏的主线程了,但是为什么没有调用CALL成功呢?
我们ctrl+g转到MessageBeep函数,在开头下个断点:

在这里插入图片描述

在这里插入图片描述

执行到MessageBeep这里,双击返回地址返回到上一层,找到MessageBeep函数调用下面的那个CALL,看看给该CALL传的参数,以及返回的eax值是否正确:

在这里插入图片描述

我们发现传的参数有问题,传的是包含"player"这个字符串变量的地址,也就是多了一层,即应该传入70A57024,结果传的却是03A2FD80。
为了测试,我们把上图右下角压入堆栈的03A2FD80修改为70A57024,再看看CALL调用是否成功:

在这里插入图片描述

主线代码中的__asm语句块,不应该用lea指令取参数1的地址,而应该直接把参数1压栈即可:

在这里插入图片描述

因为主线代码所在的DLL已经在游戏的地址空间中了,所以我们不需要再通过API函数ReadProcessMemory(为了跨进程)来读进程数据,可以直接用指针来读(速度特别的快):

在这里插入图片描述

在这里插入图片描述

测试的时候我们发现控制台窗口经常没有输出,这可能是由于被游戏给重定向了(比方说被游戏重定向到某个文件了),之后我们再修改控制台窗口的控制代码,这里可以先把护甲值输出到MessageBoxA弹窗或者编辑框中,避免了控制台被游戏给重定向。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

因为这个弹窗在主线程中,会卡主线程,它不是一下子就显示出来的,所以这个显示输出的方式我们还要另外想办法;
比方说输出调试信息的方式(结合DebugView工具查看调试信息):

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们把代码中的printf输出语句注释掉,避免被游戏重定位转到了其他地方,导致printf被卡住,调试信息输出不到DebugView。

在这里插入图片描述

在这里插入图片描述

但是我们发现,定时器挂接主线程这种方法不是太稳定,主线回调那里经常执行不进去,我们后面再优化解决这个问题。


C,C++输入输出重定向

我们可以控制printf的输出到屏幕、某个设备上,或者文件,这里我们要让它输出到控制台的屏幕上,避免被游戏给重定向到文件。

在这里插入图片描述

在这里插入图片描述

stdout, stdin, stderr的中文名字分别是标准输出,标准输入和标准错误

freopen( “CONIN$” , “r+t” , stdin); // 重定向 STDIN

freopen( “CONOUT$” , “w+t” , stdout); // 重定向STDOUT

CONOUT$表示我们控制台的屏幕,这里就是把标准的输出stdout恢复到屏幕CONOUT$,因为游戏可能也是用到这个freopen函数把我们标准的输出stdout定位到文件里面去了。

在这里插入图片描述

这句新增的代码是重新把我们的输出定位到标准输出里面,用另外的话表示,就是把printf这一类的函数输出到黑窗口屏幕上。

在这里插入图片描述

在这里插入图片描述

上图就是用来解决error C4996,通过定义这个_CRT_SECURE_NO_WARNINGS这个宏就可以继续使用不安全的函数freopen。

在这里插入图片描述

在这里插入图片描述

注入进去好像是卡住了,把代码注入器给卡住了,因为它在等着我们那个线程返回,但是由于我们这个动态链接库DLL在A031_MFC_DLL.cpp源文件中的InitInstance函数初始化这里就被卡住了:

在这里插入图片描述

但是我们的代码注入器一直在等待我们的代码执行到上图第65行return TRUE;这里,但是64行这里的.DoModal它是阻塞的,就卡在这里了,那像这种该怎么解决呢?

应该把这两句代码放到新的线程里面去执行:

在这里插入图片描述

MFC的函数必须要将AFX_MANAGE_STATE(AfxGetStaticModuleState());这个宏添加到显示窗口函数的最前面,也就是说我们在DLL中用到了MFC的窗口,那就需要把这行代码放到函数中的第一行,这个宏会做一些资源的初始化工作,我们后面在显示窗口的时候,它才能够查找到相应的资源,才能够正常的显示窗口。

如果编写的是DLL程序的话,你会发现在DLL中调用AfxGetApp这个函数会得到DLL的应用对象。原因出现在DLL的模块状态上。应用程序在调用DLL时为了保证资源不出问题,往往会调用一句:
AFX_MANAGE_STATE(AfxGetStaticModuleState()); //switch thread state back to dll
注意这是一个宏。它的作用是切换模块的全局变量范围,即把应用程序的那些全局变量拷贝切换到这个DLL的全局变量拷贝,自然用AfxGetApp得到的就是DLL里面的这个APP了。如果想用AfxGetApp这个函数访问应用程序的App对象,那么只要把模块状态切换回去就可以了,记着执行完后一定要把状态再切换回来啊,否则就要出问题了。
例如:

// switch thread state back to application
_AFX_THREAD_STATE* pState = AfxGetThreadState();
AfxSetModuleState(pState->m_pPrevModuleState);
// do something with the application
AfxGetApp()->...
// switch thread state back to dll
AFX_MANAGE_STATE(AfxGetStaticModuleState())

参考链接:https://blog.csdn.net/tianmeshi/article/details/4209904

在这里插入图片描述

这里通过CreateRemoteThread或者CreateThread创建独立的线程之后,它就不会等待代码执行到64行那里(就不会阻塞了),它会直接执行74行返回,所以就不会卡代码注入器了。
因为它到时候只会在新线程里面执行到并卡在63行这里,我们代码注入器只是等待这个InitInstance函数初始化完成,即执行CreateRemoteThread这句代码后,直接执行74行return TRUE;返回就OK了,通过CreateRemoteThread所创建的这个独立的新线程执行没执行完,就跟InitInstance的执行就没关系了(执行InitInstance函数的线程和执行显示窗口的线程不同)。

在这里插入图片描述

这时候读取护甲值就非常顺畅了,随便改变装备也能迅速把最新的护甲值给读取出来。

通过控制台重定向和创建新线程解决界面阻塞,就解决了上节课控制台输出不稳定的问题。

我们也可以把读取的值写到C盘123.txt文件里面:

在这里插入图片描述

在这里插入图片描述

还有一个问题,我们每次注入DLL之后,都要通过上图这个代码注入器这里卸载注入的DLL,每次都要手动的这样做也是很麻烦的,我们可以添加一句代码,在窗口关闭的时候,我们就用代码来卸载掉这个动态链接库DLL:

在这里插入图片描述

只要注入的窗口一直在显示的话,63行之后的代码就不会被执行(阻塞在63行.DoModal()函数这里),直到这个窗口关闭之后才会执行到后面的代码,卸载掉这个DLL并退出这个线程。

在这里插入图片描述

这里我们又发现了一个问题,在我们点击读取护甲值的按钮之后,写入到了C盘123.txt文件里面,但是我们在C盘看到123.txt文件的大小是0KB,这是由于缓存的原因导致数据没有立即写入到文件,我们可以通过fclose函数关闭一下控制台即可立即写入文件,如下修改代码:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这种立即写入文件的方式要修改很多代码,这里我们还是先用简单的输出到控制台的方式吧(把上面2张图修改的代码注释掉)。

在这里插入图片描述


C,C++定时器与主线程

在这里插入图片描述

定时器,顾名思义就是时钟的意思,也就是说它每过多长时间就去执行一下相应的代码。


定时器(微软文档)

A timer is an internal routine that repeatedly measures a specified interval, in milliseconds.
定时器是一个内部例程,用于重复测量指定的时间间隔(以毫秒为单位)。

微软参考链接:SetTimer function

  • Parameters

    • [in, optional] hWnd
      Type: HWND
      A handle to the window to be associated with the timer. This window must be owned by the calling thread. If a NULL value for hWnd is passed in along with an nIDEvent of an existing timer, that timer will be replaced in the same way that an existing non-NULL hWnd timer will be.
      与计时器关联的窗口句柄。该窗口必须为调用线程所有。如果将 hWnd 的 NULL 值与现有定时器的 nIDEvent 一起传入,则该定时器将以与现有的非 NULL hWnd 定时器一样被替换。

    • [in] nIDEvent
      Type: UINT_PTR
      A nonzero timer identifier. If the hWnd parameter is NULL, and the nIDEvent does not match an existing timer then it is ignored and a new timer ID is generated. If the hWnd parameter is not NULL and the window specified by hWnd already has a timer with the value nIDEvent, then the existing timer is replaced by the new timer. When SetTimer replaces a timer, the timer is reset. Therefore, a message will be sent after the current time-out value elapses, but the previously set time-out value is ignored. If the call is not intended to replace an existing timer, nIDEvent should be 0 if the hWnd is NULL.
      一个非零的定时器标识符。如果 hWnd 参数为 NULL,且 nIDEvent 与现有定时器不匹配,则该nIDEvent参数会被忽略并生成一个新的定时器标识符。如果 hWnd 参数不为 NULL,且 hWnd 指定的窗口已有一个值为 nIDEvent 的定时器,则现有定时器将被新定时器取代。当SetTimer 替换定时器时,定时器将被重置。因此,在当前超时值uElapse结束后,将发送一条信息,但之前设置的超时值将被忽略。如果调用的目的不是替换现有的定时器,则如果 hWnd 为 NULL,则 nIDEvent 应为 0。

    • [in] uElapse
      Type: UINT
      The time-out value, in milliseconds.
      超时值,以毫秒为单位。

    • [in, optional] lpTimerFunc
      Type: TIMERPROC
      A pointer to the function to be notified when the time-out value elapses. For more information about the function, see TimerProc. If lpTimerFunc is NULL, the system posts a WM_TIMER message to the application queue. The hwnd member of the message’s MSG structure contains the value of the hWnd parameter.
      超时值(uElapse)过期时要通知的函数指针。有关函数的更多信息,请参阅 TimerProc。 如果 lpTimerFunc 为 NULL,系统将向应用程序队列发送一条 WM_TIMER 消息。消息 MSG 结构的 hwnd 成员包含 hWnd 参数的值。
      The timer identifier, nIDEvent, is specific to the associated window. Another window can have its own timer which has the same identifier as a timer owned by another window. The timers are distinct.
      计时器标识符nIDEvent是特定于关联的窗口。另一个窗口可以拥有自己的计时器,该计时器具有与另一个窗口拥有的计时器相同的标识符。计时器是不同的。
      也就是说不同窗口的定时器标识符可以相同,它们是通过各自关联的窗口句柄来区分(定时器标识符是各窗口私有的)。

  • Return value

    • Type: UINT_PTR
      If the function succeeds and the hWnd parameter is NULL, the return value is an integer identifying the new timer. An application can pass this value to the KillTimer function to destroy the timer.
      如果函数执行成功,且 hWnd 参数为 NULL,则返回值是一个标识新定时器的整数。应用程序可将此值传递给 KillTimer 函数,以销毁定时器。
      If the function succeeds and the hWnd parameter is not NULL, then the return value is a nonzero integer. An application can pass the value of the nIDEvent parameter to the KillTimer function to destroy the timer.
      如果函数执行成功,且 hWnd 参数不是 NULL,那么返回值就是一个非零整数。应用程序可将 nIDEvent 参数的值传递给 KillTimer 函数,以销毁定时器。
      If the function fails to create a timer, the return value is zero. To get extended error information, call GetLastError.
      如果函数未能创建定时器,则返回值为零。要获取扩展错误信息,请调用 GetLastError。
  • Remarks
    An application can process WM_TIMER messages by including a WM_TIMER case statement in the window procedure or by specifying a TimerProc callback function when creating the timer. When you specify a TimerProc callback function, the DispatchMessage calls the callback function instead of calling the window procedure when it processes WM_TIMER with a non-NULL lParam. Therefore, you need to dispatch messages in the calling thread, even when you use TimerProc instead of processing WM_TIMER.
    应用程序可以通过在窗口过程中包含WM_TIMER case语句或在创建计时器时指定TimerProc回调函数来处理WM_TIMER消息。当您指定TimerProc回调函数时,DispatchMessage会在使用非空lParam处理WM_TIMER时调用回调函数,而不是调用窗口过程。因此,您需要在调用线程中调度消息,即使您使用TimerProc而不是处理WM_TIMER。
    The wParam parameter of the WM_TIMER message contains the value of the nIDEvent parameter.
    WM_TIMER消息的wParam参数包含nIDEvent参数的值。
    The timer identifier, nIDEvent, is specific to the associated window. Another window can have its own timer which has the same identifier as a timer owned by another window. The timers are distinct.
    定时器标识符 nIDEvent 是特定于相关窗口的。另一个窗口可以拥有自己的定时器,其标识符与另一个窗口拥有的定时器相同。这些定时器是不同的。
    SetTimer can reuse timer IDs in the case where hWnd is NULL.
    SetTimer可以在hWnd为NULL的情况下重用定时器id。
    If your application creates a timer without specifying a window handle, your application must monitor the message queue for WM_TIMER messages and dispatch them to the appropriate window.
    如果你的应用程序在创建定时器时没有指定窗口句柄,则你的应用程序必须监控消息队列中的 WM_TIMER 消息,并将其分派到相应的窗口。

  • Timer Notifications

    • WM_TIMER
      Posted to the installing thread’s message queue when a timer expires. The message is posted by the GetMessage or PeekMessage function.
      当计时器到期时,发布到安装线程的消息队列中。该消息由GetMessage或PeekMessage函数发布。

// MyTimerProc is an application-defined callback function that processes WM_TIMER messages.
VOID CALLBACK MyTimerProc(
HWND hwnd, // handle to window for timer messages
UINT message, // WM_TIMER message
UINT idTimer, // timer identifier
DWORD dwTime) // current system time


代码实例

这里我们添加一个按钮,用来创建一个定时器,并把定时器挂在按钮所在的这里窗口上,因为定时器都要求有一个窗口来接收定时器的消息:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

第141行的代码每过1秒钟就执行一次,这就是定时器的作用。

我们修改代码,让这个定时执行的函数每按一次按钮才执行一次,同时在该函数里面打印一下当前线程的ID,以便和执行主线代码的线程ID进行比较:

在这里插入图片描述

你也可以修改printf那句代码,把m_hWnd和hWnd都各自打印一下看看。

在这里插入图片描述

这是我们创建的线程(线程ID为25DC),而打印的主线代码的线程ID其实是游戏主线程的线程ID(20D4):

在这里插入图片描述

在这里插入图片描述

我们可以看到主线程的ID为20D4。

在这里插入图片描述

上图这个线程25DC是我们自己注入的,我们可以到该线程的入口地址那里看看:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

从上图可以看到,显示窗口函数正是我们自己创建的线程的线程函数。

在这里插入图片描述

如果我们的代码(创建定时器)传入的不是游戏的窗口句柄,那么肯定就不能挂到游戏的主线程上面,那么我们的代码就不能正常的跑起来。
有的CALL需要挂主线程,有的CALL不需要的,这要看它的CALL里面有没有在多个线程里面相互的传递数据,大部分游戏都是需要挂接主线程才能正常的跑。


基址偏移分析、角色信息复习

在这里插入图片描述

在CE中也可以查看当前函数的堆栈视图:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

怎么快速找到当前函数的上一层CALL,方法有很多,比如下断、然后在堆栈中return返回地址(最保险),为准确起见,要下条件断点;或者执行到返回,但是容易跑崩、跟飞(尤其游戏有保护的情况下);
或者如下图所示,搜索当前模块、常数,输入该函数地址(找下图这个函数72CC30的上一层),会搜到CALL该函数地址的位置:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们在堆栈窗口中可以看到黑色的竖线条,每一段线条叫做栈帧,每个栈帧代表一个CALL的环境。

在这里插入图片描述

我们在上图堆栈窗口选中的那行返回地址处双击,反汇编窗口就会定位到49545B这条返回指令上,即该指令上方的CALL wow.494F30指令就是这个返回地址的CALL,堆栈窗口中3BDFE04到3BDFDE8这段栈帧就是wow.494F30这个CALL的栈帧(下图选中的部分):

在这里插入图片描述

我们之前的课讲过,在下图左下角的内存窗口(数据窗口)2AA8883C那里下一个硬件访问断点(1字节或者4字节),当打开角色信息面板就会断在反汇编窗口4F54E1那条指令,它上面4F54DA地址处的指令mov esi, dword ptr ds:[eax+edx*4+174]就访问了2AA8883C地址处的内容,该指令上面有3条push指令,所以下图右下角堆栈窗口那里选中的有红字的那行就是返回地址,即上一层CALL的位置:

在这里插入图片描述

在这里插入图片描述

如上图选中的那条指令所示,我们要往上找esi的来源:

在这里插入图片描述

从上图我们看到,eax里面放的就是那个"player"字符串,然后按F8执行到00610F01这里的时候:

在这里插入图片描述

如上图所示,点击一下内存窗口,然后按ctrl+G,输入将在内存窗口中转到的表达式...里面的公式计算出来的值正好就是护甲值的地址2AA8883C。

那个游戏的控制台如果你直接关闭的话,wow游戏也会跟着退出,所以要用FreeConsole();来关闭游戏的控制台。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1045221.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Controller内存马

获取当前上下文运行环境 getCurrentWebApplicationContext WebApplicationContext context ContextLoader.getCurrentWebApplicationContext(); 在SpringMVC环境下获取到的是一个XmlWebApplicationContext类型的Root WebApplicationContext: 在Spring MVC环境中…

Armv9读取cache内容:Direct access to internal memory

10 访问cache Cortex-A720核心提供一种机制,通过IMPLEMENTATION DEFINED系统寄存器可以读取L1缓存、L2缓存和Translation Lookaside Buffer(TLB)。当缓存数据与系统内存数据之间的一致性异常时,您可以使用此机制来调查任何问题。 只有在EL3中才可以访问内部内存(cache)。…

spring 2.2.9源码构建注意事项

这里第一点是 grable的构建总失败,所以把pom中的这个模块删除,同时我也把这个工程删除了。 还有是pom里加一个插件的标签它的意思大概是忽略一个下载的东西那个也总是导致失败! 还就是编译maven编译时的jdk版本和实际运行时的差别不要差别太…

虹科分享 | 为工业机器人解绑,IO-Link wireless无线通讯技术可实现更加轻量灵活的机器人协作

背景 机器人是一种能够半自主或全自主工作的智能机器。中国电子学会组织发布的《中国机器人产业发展报告(2022年)显示,近些年,我国机器人市场规模持续快速增长,“机器人”应用不断拓展深入,预计五年年均增…

论文学习:RT-DETR

RT-DETR 摘要 DETR取得显著性能,但高成本计算使其无法发挥无NMS的优势,无法实际应用。本文分析了NMS对准确性和速度的负面影响,并建立端到端的速度基准。第一个实时端到端检测器,高效处理多尺度特征,并提出IoU-aware…

大型IT系统的UML类图设计实践与管理

导言: 在现代软件开发中,建立大型IT系统的UML类图是一项至关重要的任务。这些类图扮演了关键角色,帮助开发团队理清系统的结构、功能和关系。然而,随着系统规模的增大,类图的设计和管理变得复杂起来。本文将探讨一些关…

Python——— 异常机制

(一)异常 工作中,程序遇到的情况不可能完美。比如:程序要打开某个文件,这个文件可能不存在或者文件格式不对;程序在运行着,但是内存或硬盘可能满了等等。 软件程序在运行过程中,非常…

8、SpringBoot_多环境开发

二、多环境开发 1.概述 概述:开发环境、测试环境、生产环境 分类 开发环境 spring:datasource:druid:url: jdbc:mysql://localhost:3306/springboot_ssmusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver测试环境 spring:datasource:dr…

[WUSTCTF2020]CV Maker 文件头检查

这道很简单 首先注册登入 很显然是我们文件上传 我们直接随便上传一个看看 报错了我们去看看 这个 exif是什么 就是检查文件头 那我们直接修改文件头上传即可 GIF89a <script language"php">eval($_POST[cmd]); </script> 上传修改php即可

全网最全Python系列教程(非常详细)---字符串讲解(学Python入门必收藏)

&#x1f9e1;&#x1f9e1;&#x1f9e1;这篇是关于Python中字符串的讲解&#xff0c;涉及到以下内容&#xff0c;欢迎点赞和收藏&#xff0c;你点赞和收藏是我更新的动力&#x1f9e1;&#x1f9e1;&#x1f9e1; 本文将从以下几个方面展开对字符串的讲解&#xff1a; 1、字…

如何在Python中实现安全的密码存储与验证

在现代互联网时代&#xff0c;安全性已经成为一个非常重要的问题。在我们的日常生活中&#xff0c;我们会使用许多网站和应用程序&#xff0c;而这些网站和应用程序通常要求我们提供密码来保护我们的个人信息。然而&#xff0c;密码泄露事件时有发生&#xff0c;我们经常听到关…

@ConditionalOnProperty配置属性作为条件

1.ConditionalOnProperty​做什么用的&#xff1f; 主要是根据配置参数&#xff0c;来决定是否需要创建这个bean&#xff0c;这样就给了我们一个根据配置来控制Bean的选择的手段了&#xff0c;不启用只需要更改配置即可。 ​ConditionalOnProperty​源码 package org.springf…

进程管理--CFS调度器(1)

介绍 CFS&#xff08;Completely Fair Scheduler&#xff0c;完全公平调度器)用于Linux系统中普通进程的调度。它给cfs_rq&#xff08;cfs的run queue&#xff09;中的每一个进程设置一个虚拟时钟&#xff0c;vruntime。如果一个进程得以执行&#xff0c;随着时间的增长&#…

Pycharm在进行debug时出现collecting data如何解决?

Pycharm在进行debug时变量界面出现collecting data&#xff0c;问题如下&#xff1a; 解决方法&#xff1a;打开Setting界面&#xff0c;在Python Debugger选项中勾选下图中的Gevent compatible即可。

iOS CocoaPod 打包:SDK开发、Pod组件生成等

参考链接&#xff1a;CocoaPod打包 SDK开发 - 简书 iOS非集成打包&#xff1a;依赖cocoapods的Swift静态库打包、脚本合并真机与模拟器 - 简书 iOS 组件化开发----pod私有库制作及使用_ios组件化开发-CSDN博客 1.生成pod包命令 pod lib create testTools 如果提示&#xf…

img 固定宽高 图像不拉伸 显示图片中间部分

.m-sd-chat-select-avatar-img{width: 100px;height: 125px;object-fit: cover;border-radius: 6px;cursor: pointer;} 使用后&#xff1a; 使用前&#xff1a;

Django 联表查询操作

在日常的开发中&#xff0c;常常需要对多张数据表同时进行数据查询。多表查询需要在数据表之间建立表关系才能够实现。一对多或一对一的表关系是通过外键实现关联的&#xff0c;而多表查询分为正向查询和反向查询。 表模型结构 以歌手表、专辑表、单曲表查询为例子。 歌手与专…

RK3588 VDD_LOGIC电源PCB设计注意事项

RK3588 VDD_LOGIC电源PCB设计 1、VDD_LOGIC的覆铜宽度需满足芯片的电流需求&#xff0c;连接到芯片电源管脚的覆铜足够宽&#xff0c;路径不能被过孔分割太严重&#xff0c;必须计算有效线宽&#xff0c;确认连接到CPU每个电源PIN脚路径都足够。 2、如图1所示&#xff0c;原理…

Scrapy-应对反爬虫机制

参考自https://blog.csdn.net/y472360651/article/details/130002898 记得把BanSpider改成自己的项目名&#xff0c;还有一个细节要改一下&#xff0c;把代码user换成user_agent 禁止Cookie 在Scrapy项目中的settings文件&#xff0c;可以发现文件中有以下代码: COOKIES_ENA…

红黑树-自平衡二叉搜索树

一、简介 红黑树&#xff08;Red-Black Tree&#xff09;是一种自平衡的二叉搜索树&#xff0c;它的节点可以是红色或黑色。这个颜色的设计是为了满足红黑树的五个关键性质&#xff0c;确保树保持平衡和高效地支持插入、删除和搜索操作。 以下是红黑树的五个关键性质&#xf…