From ff760ae4bb7257515cd4fcec74e537613ebb4be2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Feb 2026 13:33:16 +0000 Subject: [PATCH] archive: add 5 repo prompt(s) [skip ci] --- archive/gmh5225/Disable-nmi-callbacks.txt | 194 + archive/gmh5225/Kernel_Anti-Cheat.txt | 1310 +++ archive/gmh5225/NMI-nmi_callback.txt | 551 + archive/gmh5225/NMICallbackBlocker2.txt | 216 + archive/jlgreathouse/AMD_IBS_Toolkit.txt | 10526 ++++++++++++++++++++ 5 files changed, 12797 insertions(+) create mode 100644 archive/gmh5225/Disable-nmi-callbacks.txt create mode 100644 archive/gmh5225/Kernel_Anti-Cheat.txt create mode 100644 archive/gmh5225/NMI-nmi_callback.txt create mode 100644 archive/gmh5225/NMICallbackBlocker2.txt create mode 100644 archive/jlgreathouse/AMD_IBS_Toolkit.txt diff --git a/archive/gmh5225/Disable-nmi-callbacks.txt b/archive/gmh5225/Disable-nmi-callbacks.txt new file mode 100644 index 00000000..f447acb6 --- /dev/null +++ b/archive/gmh5225/Disable-nmi-callbacks.txt @@ -0,0 +1,194 @@ +Project Path: arc_gmh5225_Disable-nmi-callbacks_62ekmd51 + +Source Tree: + +```txt +arc_gmh5225_Disable-nmi-callbacks_62ekmd51 +├── README.md +└── driver.cpp + +``` + +`README.md`: + +```md +# Disable_nmi_callbacks +# an old code +# + +```C + +extern "C" +{ + + NTSYSAPI BOOLEAN NTAPI KeInterlockedSetProcessorAffinityEx(PKAFFINITY_EX pAffinity, KEPROCESSORINDEX idxProcessor); + +} + +bool disable_nmi_callbacks() { + const auto ntoskrnl_base = (PVOID)utils::get_kernel_module(Crypt("ntoskrnl.exe")); + + if (!ntoskrnl_base) { + DbgPrintEx(0, 0, Crypt("[-] ntoskrnl_base not found\n")); + return 0; + } + else { + DbgPrintEx(0, 0, Crypt("[+] ntoskrnl_base @ 0x%p\n"), ntoskrnl_base); + + } + + auto nmi_in_progress = reinterpret_cast(utils::find_pattern((uintptr_t)ntoskrnl_base, Crypt("\x81\x25\x00\x00\x00\x00\x00\x00\x00\x00\xB9\x00\x00\x00\x00"), Crypt("xx????????x????"))); + + if (!nmi_in_progress) { + DbgPrintEx(0, 0, Crypt("[-] nmi_in_progress not found\n")); + return 0; + } + else { + DbgPrintEx(0, 0, Crypt("[+] nmi_in_progress @ 0x%p\n"), nmi_in_progress); + } + + if (nmi_in_progress) { + + while (*nmi_in_progress != 0x48) { + ++nmi_in_progress; + } + + nmi_in_progress = impl::resolve_mov(nmi_in_progress); + + DbgPrintEx(0, 0, Crypt("[+] nmi_in_progress (resolved) @ 0x%p\n"), nmi_in_progress); + + if (!nmi_in_progress) { + DbgPrintEx(0, 0, Crypt("[-] !nmi_in_progress\n")); + } + + auto irql = KfRaiseIrql(0); + + ULONG cores = KeQueryActiveProcessorCount(NULL); + + for (auto i = 0ul; i < cores; ++i) { + + KeInterlockedSetProcessorAffinityEx((PKAFFINITY_EX)nmi_in_progress, i); + InterlockedBitTestAndSet64((LONG64*)(nmi_in_progress), i); + + DbgPrintEx(0, 0, Crypt("[+] disabled nmi for proccessor %d\n"), i); + + } + + KeLowerIrql(irql); + } + + DbgPrintEx(0, 0, Crypt("[+] Done disabled nmi callback\n")); + return true; + +} + +``` + +# Example Usage + +```C + +extern "C" NTSTATUS DriverEntry() { + + BOOL status = disable_nmi_callbacks(); + + if (status == FALSE) { + DbgPrintEx(0, 0, Crypt("[-] Failed disabling nmi callbacks.\n")); + } + else { + DbgPrintEx(0, 0, Crypt("[+] Done disabled nmi callback\n")); + } + + + DbgPrintEx(0, 0, Crypt("[+] Driver loaded!\n")); + +} + +``` + +``` + +`driver.cpp`: + +```cpp +extern "C" +{ + + NTSYSAPI BOOLEAN NTAPI KeInterlockedSetProcessorAffinityEx(PKAFFINITY_EX pAffinity, KEPROCESSORINDEX idxProcessor); + +} + +bool disable_nmi_callbacks() { + const auto ntoskrnl_base = (PVOID)utils::get_kernel_module(Crypt("ntoskrnl.exe")); + + if (!ntoskrnl_base) { + DbgPrintEx(0, 0, Crypt("[-] ntoskrnl_base not found\n")); + return 0; + } + else { + DbgPrintEx(0, 0, Crypt("[+] ntoskrnl_base @ 0x%p\n"), ntoskrnl_base); + + } + + auto nmi_in_progress = reinterpret_cast(utils::find_pattern((uintptr_t)ntoskrnl_base, Crypt("\x81\x25\x00\x00\x00\x00\x00\x00\x00\x00\xB9\x00\x00\x00\x00"), Crypt("xx????????x????"))); + + if (!nmi_in_progress) { + DbgPrintEx(0, 0, Crypt("[-] nmi_in_progress not found\n")); + return 0; + } + else { + DbgPrintEx(0, 0, Crypt("[+] nmi_in_progress @ 0x%p\n"), nmi_in_progress); + } + + if (nmi_in_progress) { + + while (*nmi_in_progress != 0x48) { + ++nmi_in_progress; + } + + nmi_in_progress = impl::resolve_mov(nmi_in_progress); + + DbgPrintEx(0, 0, Crypt("[+] nmi_in_progress (resolved) @ 0x%p\n"), nmi_in_progress); + + if (!nmi_in_progress) { + DbgPrintEx(0, 0, Crypt("[-] !nmi_in_progress\n")); + } + + auto irql = KfRaiseIrql(0); + + ULONG cores = KeQueryActiveProcessorCount(NULL); + + for (auto i = 0ul; i < cores; ++i) { + + KeInterlockedSetProcessorAffinityEx((PKAFFINITY_EX)nmi_in_progress, i); + InterlockedBitTestAndSet64((LONG64*)(nmi_in_progress), i); + + DbgPrintEx(0, 0, Crypt("[+] disabled nmi for proccessor %d\n"), i); + + } + + KeLowerIrql(irql); + } + + DbgPrintEx(0, 0, Crypt("[+] Done disabled nmi callback\n")); + return true; + +} + + +extern "C" NTSTATUS DriverEntry() { + + BOOL status = disable_nmi_callbacks(); + + if (status == FALSE) { + DbgPrintEx(0, 0, Crypt("[-] Failed disabling nmi callbacks.\n")); + } + else { + DbgPrintEx(0, 0, Crypt("[+] Done disabled nmi callback\n")); + } + + + DbgPrintEx(0, 0, Crypt("[+] Driver loaded!\n")); + +} +``` \ No newline at end of file diff --git a/archive/gmh5225/Kernel_Anti-Cheat.txt b/archive/gmh5225/Kernel_Anti-Cheat.txt new file mode 100644 index 00000000..df3d5a0e --- /dev/null +++ b/archive/gmh5225/Kernel_Anti-Cheat.txt @@ -0,0 +1,1310 @@ +Project Path: arc_gmh5225_Kernel_Anti-Cheat_tu_4iq3p + +Source Tree: + +```txt +arc_gmh5225_Kernel_Anti-Cheat_tu_4iq3p +├── README.md +└── ac + ├── ac.filters + ├── ac.inf + ├── ac.sln + ├── ac.user + ├── ac.vcxproj + ├── defs.h + ├── handles.c + ├── hypervisor.c + ├── main.c + ├── nmi.c + ├── pool.c + ├── serial.c + ├── threads.c + ├── traces.c + └── utils.c + +``` + +`README.md`: + +```md +# *Legal Disclaimer +***The use of this project is the sole responsibility of the user. I am not responsible for any consequences arising from the use of this tool.*** + +# Features : + + NMI StackWalking + Hypervisor Detection + Big Pool scanning + UUID + Scanning system threads + Checking PIDDBCacheTable for KDmapper & Drvmap + + +# The NMI Stackwalking : +First we register an NMI callback that will be called when a hardware interupt occurs. inside the NMI callback you can see we capture the stack using RtlCaptureStackBackTrace and store the results in a list. +**code:** + BOOLEAN NmiCallback(PVOID context, BOOLEAN handled) + { + UNREFERENCED_PARAMETER(context); + UNREFERENCED_PARAMETER(handled); + + PVOID* stackTrace = ExAllocatePoolWithTag(NonPagedPool, 0x1000, AC_POOL_TAG); + + if (!stackTrace) + return TRUE; + + //Walk the stack and record information for each frame + USHORT capturedFrames = RtlCaptureStackBackTrace(0, 0x1000 / 8, stackTrace, NULL); + + // Loop through g_PageOfpStackWalkResult list, + // if empty it has been checked by the DetectionThread, + // and we can store the stackTrace and the capturedFrames in the list. + for (int i = 0; i < 0x1000 / 0x10; i += 2) + { + if (((DWORD64*)g_PageOfpStackWalkResult)[i] == 0) + { + ((DWORD64*)g_PageOfpStackWalkResult)[i] = (ULONG64)stackTrace; + ((DWORD64*)g_PageOfpStackWalkResult)[i + 1] = capturedFrames; + break; + } + } + + return TRUE; + } + +Then when we get the results inside our list we can iterate through it and check one of the frame values is not in a valid module. +**Code:** + + // Loop through the g_PageOfpStackWalkResult list + for (INT i = 0; i < 0x1000 / 0x10; i += 2) + { + // Check if current item of g_PageOfpStackWalkResult is there + if (((DWORD64*)g_PageOfpStackWalkResult)[i] == 0) + continue; + + // Check if the stackTrace is valid and there are captured frames to loop through + if (MmIsAddressValid(((PVOID*)g_PageOfpStackWalkResult)[i]) && ((DWORD64*)g_PageOfpStackWalkResult)[i + 1]) + { + // Loop through the captured frames + for (SIZE_T j = 0; i < ((DWORD64*)g_PageOfpStackWalkResult)[i + 1]; j++) + { + ULONG64 CurrentFrameValue = (((DWORD64**)g_PageOfpStackWalkResult)[i])[j]; + + // Check if the CurrentFrameValue is an address in kernel + if (CurrentFrameValue < 0xFFFF000000000000) + break; + + // Check if CurrentFrameValue is in a kernel module. + // If not, we have found something. + if (IsAdressOutsideModulelist(CurrentFrameValue)) + DbgPrintEx(0, 0, "Unsigned code : %llx", CurrentFrameValue); + } + } + + // Remove the stackTrace and the capturedFrames out of the list, + // So the NMI callback can store new ones. + ExFreePoolWithTag(((PVOID*)g_PageOfpStackWalkResult)[i], AC_POOL_TAG); + ((DWORD64*)g_PageOfpStackWalkResult)[i] = 0; + ((DWORD64*)g_PageOfpStackWalkResult)[i + 1] = 0; + } + +At last we can fire an NMI using HalSendNMI whenever we want to. +**Code:** + + BOOLEAN FireNMI(INT core, PKAFFINITY_EX affinity) + { + KeInitializeAffinityEx(affinity); + KeAddProcessorAffinityEx(affinity, core); + + HalSendNMI(affinity); + + return TRUE; + } + + // Fire an NMI for eac core on the system with 100 ms delay + for (ULONG i = 0; i < KeQueryActiveProcessorCountEx(0); i++) + { + FireNMI(i, g_NmiAffinity); + DelayExecutionThread(100); + } + +- Improve the NMI callback +- Improve the SystemThread scanning +- Maybe add more features + +``` + +`ac/ac.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + + + Driver Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + +``` + +`ac/ac.inf`: + +```inf +; +; ac.inf +; + +[Version] +Signature="$WINDOWS NT$" +Class=sd ; TODO: edit Class +ClassGuid={c498d9b1-b2d0-4149-9434-9444230a43fe} ; TODO: edit ClassGuid +Provider=%ManufacturerName% +CatalogFile=ac.cat +DriverVer= ; TODO: set DriverVer in stampinf property pages +PnpLockDown=1 + +[DestinationDirs] +DefaultDestDir = 12 +ac_Device_CoInstaller_CopyFiles = 11 + +; ================= Class section ===================== + +[ClassInstall32] +Addreg=SampleClassReg + +[SampleClassReg] +HKR,,,0,%ClassName% +HKR,,Icon,,-5 + +[SourceDisksNames] +1 = %DiskName%,,,"" + +[SourceDisksFiles] +ac.sys = 1,, +WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll=1 ; make sure the number matches with SourceDisksNames + +;***************************************** +; Install Section +;***************************************** + +[Manufacturer] +%ManufacturerName%=Standard,NT$ARCH$ + +[Standard.NT$ARCH$] +%ac.DeviceDesc%=ac_Device, Root\ac ; TODO: edit hw-id + +[ac_Device.NT] +CopyFiles=Drivers_Dir + +[Drivers_Dir] +ac.sys + +;-------------- Service installation +[ac_Device.NT.Services] +AddService = ac,%SPSVCINST_ASSOCSERVICE%, ac_Service_Inst + +; -------------- ac driver install sections +[ac_Service_Inst] +DisplayName = %ac.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\ac.sys + +; +;--- ac_Device Coinstaller installation ------ +; + +[ac_Device.NT.CoInstallers] +AddReg=ac_Device_CoInstaller_AddReg +CopyFiles=ac_Device_CoInstaller_CopyFiles + +[ac_Device_CoInstaller_AddReg] +HKR,,CoInstallers32,0x00010000, "WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,WdfCoInstaller" + +[ac_Device_CoInstaller_CopyFiles] +WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll + +[ac_Device.NT.Wdf] +KmdfService = ac, ac_wdfsect +[ac_wdfsect] +KmdfLibraryVersion = $KMDFVERSION$ + +[Strings] +SPSVCINST_ASSOCSERVICE= 0x00000002 +ManufacturerName="" ;TODO: Replace with your manufacturer name +ClassName="Samples" ; TODO: edit ClassName +DiskName = "ac Installation Disk" +ac.DeviceDesc = "ac Device" +ac.SVCDESC = "ac Service" + +``` + +`ac/ac.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32922.545 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ac", "ac.vcxproj", "{3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|ARM.ActiveCfg = Debug|ARM + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|ARM.Build.0 = Debug|ARM + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|ARM.Deploy.0 = Debug|ARM + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|ARM64.Build.0 = Debug|ARM64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|x64.ActiveCfg = Debug|x64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|x64.Build.0 = Debug|x64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|x64.Deploy.0 = Debug|x64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|x86.ActiveCfg = Debug|Win32 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|x86.Build.0 = Debug|Win32 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Debug|x86.Deploy.0 = Debug|Win32 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|ARM.ActiveCfg = Release|ARM + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|ARM.Build.0 = Release|ARM + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|ARM.Deploy.0 = Release|ARM + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|ARM64.ActiveCfg = Release|ARM64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|ARM64.Build.0 = Release|ARM64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|ARM64.Deploy.0 = Release|ARM64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|x64.ActiveCfg = Release|x64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|x64.Build.0 = Release|x64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|x64.Deploy.0 = Release|x64 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|x86.ActiveCfg = Release|Win32 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|x86.Build.0 = Release|Win32 + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3}.Release|x86.Deploy.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {340E45E7-F88E-4806-896E-6B37B6B05EAC} + EndGlobalSection +EndGlobal + +``` + +`ac/ac.user`: + +```user + + + + +``` + +`ac/ac.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + {3B5F4A5D-34F4-4ACB-8012-BBBE7A97CFA3} + {1bc93793-694f-48fe-9372-81e2b05556fd} + v4.5 + 12.0 + Debug + Win32 + TestNMICallback + $(LatestTargetPlatformVersion) + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + false + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + false + http://timestamp.globalsign.com/scripts/timstamp.dll + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + false + + + SHA1 + + + + + SHA1 + + + stdcpp20 + stdc17 + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +`ac/defs.h`: + +```h +#pragma once + +#include +#include +#include +#include + +// Defines +#define AC_POOL_TAG 'BCmN' + +// Disable Warnings +#pragma warning(disable : 4996) +#pragma warning(disable : 4200) +#pragma warning(disable : 4310) +#pragma warning(disable : 4022) +#pragma warning(disable : 6387) +#pragma warning(disable : 6011) +#pragma warning(disable : 4201) +#pragma warning(disable : 4047) +#pragma warning(disable : 4024) + +// Structs + Enums +typedef struct _SYSTEM_HANDLE +{ + ULONG uIdProcess; + UCHAR ObjectType; + UCHAR Flags; + USHORT Handle; + PVOID pObject; + ACCESS_MASK GrantedAccess; +}SYSTEM_HANDLE; + +typedef struct _SYSTEM_HANDLE_INFORMATION { + ULONG uCount; + SYSTEM_HANDLE Handles[ANYSIZE_ARRAY]; +} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION; + +typedef struct _SYSTEM_BIGPOOL_ENTRY { + union { + PVOID VirtualAddress; + ULONG_PTR NonPaged : 1; + }; + ULONG_PTR SizeInBytes; + union { + UCHAR Tag[4]; + ULONG TagULong; + }; +} SYSTEM_BIGPOOL_ENTRY, * PSYSTEM_BIGPOOL_ENTRY; + +typedef struct _SYSTEM_BIGPOOL_INFORMATION { + ULONG Count; + SYSTEM_BIGPOOL_ENTRY AllocatedInfo[ANYSIZE_ARRAY]; +} SYSTEM_BIGPOOL_INFORMATION, * PSYSTEM_BIGPOOL_INFORMATION; + +typedef struct _SYSTEM_BOOT_ENVIRONMENT_INFORMATION { + struct _GUID BootIdentifier; + enum _FIRMWARE_TYPE FirmwareType; + unsigned __int64 BootFlags; +} SYSTEM_BOOT_ENVIRONMENT_INFORMATION, * PSYSTEM_BOOT_ENVIRONMENT_INFORMATION; + +typedef struct _KAFFINITY_EX +{ + USHORT Count; + USHORT Size; + ULONG Reserved; + ULONGLONG Bitmap[20]; +} KAFFINITY_EX, * PKAFFINITY_EX; + +typedef struct _PiDDBCacheEntry +{ + LIST_ENTRY List; + UNICODE_STRING DriverName; + ULONG TimeDateStamp; + NTSTATUS LoadStatus; + char _0x0028[16]; +} PiDDBCacheEntry, * PPiDDBCacheEntry; + +typedef struct _MM_UNLOADED_DRIVER +{ + UNICODE_STRING Name; + PVOID ModuleStart; + PVOID ModuleEnd; + ULONG64 UnloadTime; +} MM_UNLOADED_DRIVER, * PMM_UNLOADED_DRIVER; + +typedef struct _NMI_CONTEXT +{ + INT NumFired; +} NMI_CONTEXT, * PNMI_CONTEXT; + +typedef struct _RTL_PROCESS_MODULE_INFORMATION +{ + HANDLE Section; + PVOID MappedBase; + PVOID ImageBase; + ULONG ImageSize; + ULONG Flags; + USHORT LoadOrderIndex; + USHORT InitOrderIndex; + USHORT LoadCount; + USHORT OffsetToFileName; + UCHAR FullPathName[256]; +} RTL_PROCESS_MODULE_INFORMATION, * PRTL_PROCESS_MODULE_INFORMATION; + +typedef struct _RTL_PROCESS_MODULES +{ + ULONG NumberOfModules; + RTL_PROCESS_MODULE_INFORMATION Modules[1]; +} RTL_PROCESS_MODULES, * PRTL_PROCESS_MODULES; + +typedef enum _SYSTEM_INFORMATION_CLASS +{ + SystemBasicInformation, + SystemProcessorInformation, + SystemPerformanceInformation, + SystemTimeOfDayInformation, + SystemPathInformation, + SystemProcessInformation, + SystemCallCountInformation, + SystemDeviceInformation, + SystemProcessorPerformanceInformation, + SystemFlagsInformation, + SystemCallTimeInformation, + SystemModuleInformation, + SystemLocksInformation, + SystemStackTraceInformation, + SystemPagedPoolInformation, + SystemNonPagedPoolInformation, + SystemHandleInformation, + SystemObjectInformation, + SystemPageFileInformation, + SystemVdmInstemulInformation, + SystemVdmBopInformation, + SystemFileCacheInformation, + SystemPoolTagInformation, + SystemInterruptInformation, + SystemDpcBehaviorInformation, + SystemFullMemoryInformation, + SystemLoadGdiDriverInformation, + SystemUnloadGdiDriverInformation, + SystemTimeAdjustmentInformation, + SystemSummaryMemoryInformation, + SystemNextEventIdInformation, + SystemEventIdsInformation, + SystemCrashDumpInformation, + SystemExceptionInformation, + SystemCrashDumpStateInformation, + SystemKernelDebuggerInformation, + SystemContextSwitchInformation, + SystemRegistryQuotaInformation, + SystemExtendServiceTableInformation, + SystemPrioritySeperation, + SystemPlugPlayBusInformation, + SystemDockInformation, + SystemProcessorSpeedInformation, + SystemCurrentTimeZoneInformation, + SystemLookasideInformation +} SYSTEM_INFORMATION_CLASS, * PSYSTEM_INFORMATION_CLASS; + +typedef struct _SYSTEM_MODULE_ENTRY +{ + HANDLE Section; + PVOID MappedBase; + PVOID ImageBase; + ULONG ImageSize; + ULONG Flags; + USHORT LoadOrderIndex; + USHORT InitOrderIndex; + USHORT LoadCount; + USHORT OffsetToFileName; + UCHAR FullPathName[256]; +} SYSTEM_MODULE_ENTRY, * PSYSTEM_MODULE_ENTRY; + +typedef struct _SYSTEM_MODULE_INFORMATION +{ + ULONG Count; + SYSTEM_MODULE_ENTRY Module[0]; +} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION; + +typedef struct _DIRECTORY_BASIC_INFORMATION { + UNICODE_STRING ObjectName; + UNICODE_STRING ObjectTypeName; +} DIRECTORY_BASIC_INFORMATION, * PDIRECTORY_BASIC_INFORMATION; + +// Function Declarations +VOID PrintBootUUID(); +VOID StartNMI(); +VOID StopNMI(); +VOID CheckPIDDBCacheTable(); +VOID HypervisorDetection(); +VOID ScanBigPool(); +VOID ScanSystemThreads(); +VOID CheckPhysicalMemHandles(); + +VOID Log(char* text); +VOID DelayExecutionThread(INT64 ms); +VOID WaitThreadTerminate(HANDLE ThreadHandle); +VOID GetThreadStartAddress(PETHREAD ThreadObj, uintptr_t* pStartAddr); +BOOL IsAdressOutsideModulelist(uintptr_t address); +BOOL DataCompare(const BYTE* pData, const BYTE* bMask, const char* szMask); +INT64 SecInNs(INT64 ms); +PVOID ResolveRelativeAddress(PVOID Instruction, ULONG OffsetOffset, ULONG InstructionSize); +PVOID GetKernelBase(OUT PULONG pSize); +UINT64 FindPattern(UINT64 dwAddress, UINT64 dwLen, BYTE* bMask, char* szMask); +PSYSTEM_HANDLE_INFORMATION GetHandleList(); + +// Imports +VOID KeInitializeAffinityEx(PKAFFINITY_EX affinity); +VOID KeAddProcessorAffinityEx(PKAFFINITY_EX affinity, INT num); +VOID HalSendNMI(PKAFFINITY_EX affinity); +NTSTATUS MmCopyVirtualMemory(PEPROCESS SourceProcess, PVOID SourceAddress, PEPROCESS TargetProcess, PVOID TargetAddress, SIZE_T BufferSize, KPROCESSOR_MODE PreviousMode, PSIZE_T ReturnSize); +NTSTATUS ZwQuerySystemInformation(ULONG InfoClass, PVOID Buffer, ULONG Length, PULONG ReturnLength); +NTSTATUS NtQueryInformationThread(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength, PULONG ReturnLength); +__int64 __fastcall PsGetProcessSectionBaseAddress(__int64 a1); + +``` + +`ac/handles.c`: + +```c +#include "defs.h" + +VOID CheckPhysicalMemHandles() +{ + DbgPrintEx(0, 0, "Checking PhysicalMemHandles"); + + PVOID Object; + HANDLE hPhysMem; + UNICODE_STRING phys_mem_str; + OBJECT_ATTRIBUTES oaAttributes; + PSYSTEM_HANDLE_INFORMATION handles = GetHandleList(); + + RtlInitUnicodeString(&phys_mem_str, L"\\Device\\PhysicalMemory"); + InitializeObjectAttributes(&oaAttributes, &phys_mem_str, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, (HANDLE)NULL, (PSECURITY_DESCRIPTOR)NULL); + ZwOpenSection(&hPhysMem, SECTION_ALL_ACCESS, &oaAttributes); + ObReferenceObjectByHandle(hPhysMem, 1, NULL, KernelMode, &Object, NULL); + ZwClose(hPhysMem); + + __try { + for (ULONG i = 0; i < handles->uCount; i++) + { + if (handles->Handles[i].uIdProcess == 4) + continue; + + if (handles->Handles[i].pObject == Object) + { + if (!ObIsKernelHandle((HANDLE)handles->Handles[i].Handle)) + DbgPrint("Usermode PhysicalMemory handle detected, pid = %d, access = 0x%x.\n", handles->Handles[i].uIdProcess, handles->Handles[i].GrantedAccess); + } + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { } + + ObDereferenceObject(Object); + + ExFreePoolWithTag(handles, AC_POOL_TAG); + + DbgPrintEx(0, 0, "Finished Checking PhysicalMemHandles"); +} +``` + +`ac/hypervisor.c`: + +```c +#include "defs.h" + +VOID HypervisorDetection() +{ + DbgPrintEx(0, 0, "HyperVisor Detection Started : "); + + __try { + __vmx_vmread(0, 0); + DbgPrint("Detected Hypervisor\n"); + } + __except (EXCEPTION_EXECUTE_HANDLER) {} + + DbgPrintEx(0, 0, "HyperVisor Detection Finished"); +} +``` + +`ac/main.c`: + +```c +#include "defs.h" + +VOID DriverUnload(PDRIVER_OBJECT drvObj) +{ + UNREFERENCED_PARAMETER(drvObj); + + StopNMI(); + + DbgPrintEx(0, 0, "======== Vasie Meme Anticheat Unloaded ========\n"); +} + +NTSTATUS DriverEntry(PDRIVER_OBJECT drvObj, PUNICODE_STRING regPath) +{ + UNREFERENCED_PARAMETER(regPath); + + drvObj->DriverUnload = DriverUnload; + + DbgPrintEx(0, 0, "======== Vasie Meme Anticheat Entry ========\n"); + + PrintBootUUID(); + + ScanSystemThreads(); + ScanBigPool(); + + CheckPhysicalMemHandles(); + CheckPIDDBCacheTable(); + + HypervisorDetection(); + + StartNMI(); + + return STATUS_SUCCESS; +} +``` + +`ac/nmi.c`: + +```c +#include "defs.h" + +PVOID g_NmiCallbackHandle; +PKAFFINITY_EX g_NmiAffinity; +PNMI_CONTEXT g_NmiContext; +PVOID g_PageOfpStackWalkResult; +BOOLEAN NMIStop; +HANDLE SendNMIThreadHandle; + +BOOLEAN FireNMI(INT core, PKAFFINITY_EX affinity) +{ + KeInitializeAffinityEx(affinity); + KeAddProcessorAffinityEx(affinity, core); + + HalSendNMI(affinity); + + return TRUE; +} + +VOID DetectionThread(PVOID StartContext) +{ + UNREFERENCED_PARAMETER(StartContext); + + while (!NMIStop) + { + // Fire an NMI for eac core on the system with 100 ms delay + for (ULONG i = 0; i < KeQueryActiveProcessorCountEx(0); i++) + { + FireNMI(i, g_NmiAffinity); + DelayExecutionThread(100); + } + + // Loop through the g_PageOfpStackWalkResult list + for (INT i = 0; i < 0x1000 / 0x10; i += 2) + { + // Check if current item of g_PageOfpStackWalkResult is there + if (((DWORD64*)g_PageOfpStackWalkResult)[i] == 0) + continue; + + // Check if the stackTrace is valid and there are captured frames to loop through + if (MmIsAddressValid(((PVOID*)g_PageOfpStackWalkResult)[i]) && ((DWORD64*)g_PageOfpStackWalkResult)[i + 1]) + { + // Loop through the captured frames + for (SIZE_T j = 0; i < ((DWORD64*)g_PageOfpStackWalkResult)[i + 1]; j++) + { + ULONG64 CurrentFrameValue = (((DWORD64**)g_PageOfpStackWalkResult)[i])[j]; + + // Check if the CurrentFrameValue is an address in kernel + if (CurrentFrameValue < 0xFFFF000000000000) + break; + + // Check if CurrentFrameValue is in a kernel module. + // If not, we have found something. + if (IsAdressOutsideModulelist(CurrentFrameValue)) + DbgPrintEx(0, 0, "Unsigned code : %llx", CurrentFrameValue); + } + } + + // Remove the stackTrace and the capturedFrames out of the list, + // So the NMI callback can store new ones. + ExFreePoolWithTag(((PVOID*)g_PageOfpStackWalkResult)[i], AC_POOL_TAG); + ((DWORD64*)g_PageOfpStackWalkResult)[i] = 0; + ((DWORD64*)g_PageOfpStackWalkResult)[i + 1] = 0; + } + } + PsTerminateSystemThread(STATUS_SUCCESS); +} + + +BOOLEAN NmiCallback(PVOID context, BOOLEAN handled) +{ + UNREFERENCED_PARAMETER(context); + UNREFERENCED_PARAMETER(handled); + + PVOID* stackTrace = ExAllocatePoolWithTag(NonPagedPool, 0x1000, AC_POOL_TAG); + + if (!stackTrace) + return TRUE; + + // Captures a stack back trace by walking up the stack and recording the information for each frame + USHORT capturedFrames = RtlCaptureStackBackTrace(0, 0x1000 / 8, stackTrace, NULL); + + // Loop through g_PageOfpStackWalkResult list, + // if empty it has been checked by the DetectionThread, + // and we can store the stackTrace and the capturedFrames in the list. + for (int i = 0; i < 0x1000 / 0x10; i += 2) + { + if (((DWORD64*)g_PageOfpStackWalkResult)[i] == 0) + { + ((DWORD64*)g_PageOfpStackWalkResult)[i] = (ULONG64)stackTrace; + ((DWORD64*)g_PageOfpStackWalkResult)[i + 1] = capturedFrames; + break; + } + } + + return TRUE; +} + +VOID StartNMI() +{ + DbgPrintEx(0, 0, "Started NMI callback : (Could produce false positives)"); + + ULONG numCores = KeQueryActiveProcessorCountEx(0); + ULONG nmiContextLength = numCores * sizeof(NMI_CONTEXT); + + g_NmiContext = (PNMI_CONTEXT)ExAllocatePoolWithTag(NonPagedPool, nmiContextLength, AC_POOL_TAG); + g_NmiAffinity = ExAllocatePoolWithTag(NonPagedPool, sizeof(KAFFINITY_EX), AC_POOL_TAG); + g_PageOfpStackWalkResult = ExAllocatePoolWithTag(NonPagedPool, 0x1000, AC_POOL_TAG); + + g_NmiCallbackHandle = KeRegisterNmiCallback(NmiCallback, g_NmiContext); + + if (!g_NmiAffinity || !g_NmiContext || !g_NmiCallbackHandle || !g_PageOfpStackWalkResult) + return; + + memset(g_NmiContext, 0, nmiContextLength); + memset(g_PageOfpStackWalkResult, 0, 0x1000); + + PsCreateSystemThread(&SendNMIThreadHandle, 0, NULL, NULL, NULL, &DetectionThread, NULL); +} + +VOID StopNMI() +{ + + NMIStop = TRUE; + WaitThreadTerminate(SendNMIThreadHandle); + + if (g_NmiCallbackHandle) KeDeregisterNmiCallback(g_NmiCallbackHandle); + if (g_NmiAffinity) ExFreePoolWithTag(g_NmiAffinity, AC_POOL_TAG); + if (g_NmiContext) ExFreePoolWithTag(g_NmiContext, AC_POOL_TAG); + if (g_PageOfpStackWalkResult) ExFreePoolWithTag(g_PageOfpStackWalkResult, AC_POOL_TAG); + + DbgPrintEx(0, 0, "Stopped NMI callback"); + +} + +``` + +`ac/pool.c`: + +```c +#include "defs.h" + +VOID ScanBigPool() +{ + DbgPrintEx(0, 0, "Scanning Big Pool :"); + + ULONG len = 4 * 1024 * 1024; + PVOID mem = ExAllocatePoolWithTag(NonPagedPool, len, AC_POOL_TAG); + + if (!NT_SUCCESS(ZwQuerySystemInformation(0x42, mem, len, &len))) + return; + + PSYSTEM_BIGPOOL_INFORMATION pBuf = (PSYSTEM_BIGPOOL_INFORMATION)mem; + for (ULONG i = 0; i < pBuf->Count; i++) { + __try { + + if (pBuf->AllocatedInfo[i].TagULong != 'SldT') + return; + + DbgPrint("[FLAG] TdlS pooltag detected\n"); + + PVOID page = MmMapIoSpaceEx(MmGetPhysicalAddress((void*)pBuf->AllocatedInfo[i].VirtualAddress), PAGE_SIZE, PAGE_READWRITE); + + if ((uintptr_t)page + 0x184 == 0x0B024BC8B48) + DbgPrint("[DETECTION] 0x0B024BC8B48 found at pool + 0x184\n"); + + MmUnmapIoSpace(page, PAGE_SIZE); + + } + __except (EXCEPTION_EXECUTE_HANDLER) { + } + } + + ExFreePoolWithTag(mem, AC_POOL_TAG); + + DbgPrintEx(0, 0, "Finished Scanning Big Pool :"); +} +``` + +`ac/serial.c`: + +```c +#include "defs.h" + +VOID PrintBootUUID() +{ + ULONG neededSize = 8 * 1024 * 1024; + + PSYSTEM_BOOT_ENVIRONMENT_INFORMATION pBootInfo = ExAllocatePoolWithTag(NonPagedPool, neededSize, AC_POOL_TAG); + if (!pBootInfo) + return; + + if (ZwQuerySystemInformation(0x5a, pBootInfo, neededSize, 0) != STATUS_SUCCESS) + return; + + DbgPrint("UUID : %08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X\n", pBootInfo->BootIdentifier.Data1, pBootInfo->BootIdentifier.Data2, pBootInfo->BootIdentifier.Data3, pBootInfo->BootIdentifier.Data4[0], pBootInfo->BootIdentifier.Data4[1], pBootInfo->BootIdentifier.Data4[2], pBootInfo->BootIdentifier.Data4[3], pBootInfo->BootIdentifier.Data4[4], pBootInfo->BootIdentifier.Data4[5], pBootInfo->BootIdentifier.Data4[6], pBootInfo->BootIdentifier.Data4[7]); + ExFreePoolWithTag(pBootInfo, AC_POOL_TAG); + +} +``` + +`ac/threads.c`: + +```c +#include "defs.h" + +VOID ScanSystemThreads() +{ + DbgPrintEx(0, 0, "Scanning SystemThreads : (Could produce false positives)"); + + for (ULONG thrd_id = 4; thrd_id < 0x30000; thrd_id += 4) + { + PETHREAD ThreadObj; + + if (!NT_SUCCESS(PsLookupThreadByThreadId((HANDLE)thrd_id, &ThreadObj))) + continue; + + if (!PsIsSystemThread(ThreadObj) || ThreadObj == KeGetCurrentThread()) + continue; + + uintptr_t start_addr; + GetThreadStartAddress(ThreadObj, &start_addr); + + if (IsAdressOutsideModulelist(start_addr)) + DbgPrint("Startaddress not valid : %llx", start_addr); + + if (start_addr && (memcmp((void*)start_addr, "\xFF\xE1", 2) == 0)) + DbgPrint("Startaddress jmp rcx : %llx", start_addr); + } + + DbgPrintEx(0, 0, "Stopped Scanning SystemThreads"); +} +``` + +`ac/traces.c`: + +```c +#include "defs.h" + +VOID CheckPIDDBCacheTable() +{ + DbgPrintEx(0, 0, "Scanning PIDDBCacheTable : "); + + PVOID base = GetKernelBase(NULL); + if (!base) + return; + + UINT64 PiDDBLockPtr = FindPattern((UINT64)base, (UINT64)0xFFFFFFFFFF, (BYTE*)"\x48\x8D\x0D\x00\x00\x00\x00\xE8\x00\x00\x00\x00\x4C\x8B\x8C", "xxx????x????xxx"); + if (!PiDDBLockPtr) + return; + + UINT64 PiDDBCacheTablePtr = FindPattern((UINT64)base, (UINT64)0xFFFFFFFFFF, (BYTE*)"\x66\x03\xD2\x48\x8D\x0D", "xxxxxx"); + if (!PiDDBCacheTablePtr) + return; + + PERESOURCE PiDDBLock; PRTL_AVL_TABLE table; + + PiDDBCacheTablePtr = ((uintptr_t)PiDDBCacheTablePtr + 3); + + PiDDBLock = (PERESOURCE)(ResolveRelativeAddress((PVOID)PiDDBLockPtr, 3, 7)); + table = (PRTL_AVL_TABLE)(ResolveRelativeAddress((PVOID)PiDDBCacheTablePtr, 3, 7)); + + ExAcquireResourceExclusiveLite(PiDDBLock, TRUE); + + for (PiDDBCacheEntry* p = (PiDDBCacheEntry*)RtlEnumerateGenericTableAvl(table, TRUE); + p != NULL; + p = (PiDDBCacheEntry*)RtlEnumerateGenericTableAvl(table, FALSE)) { + if (p->TimeDateStamp == 0x5284eac3) + DbgPrint("kdmapper detected, driver: %wZ\n", p->DriverName); + if (p->TimeDateStamp == 0x57CD1415) + DbgPrint("drvmap detected, driver: %wZ\n", p->DriverName); + } + + ExReleaseResourceLite(PiDDBLock); + + DbgPrintEx(0, 0, "Finished Scanning PIDDBCacheTable"); +} + +``` + +`ac/utils.c`: + +```c +#include "defs.h" + +INT64 SecInNs(INT64 ms) +{ + return (ms * 10000); +} + +VOID DelayExecutionThread(INT64 ms) +{ + LARGE_INTEGER nDelay; + memset(&nDelay, 0, sizeof(nDelay)); + + nDelay.QuadPart -= SecInNs(ms); + + KeDelayExecutionThread(KernelMode, FALSE, &nDelay); +} + +VOID WaitThreadTerminate(HANDLE ThreadHandle) +{ + if (ThreadHandle != NULL) + { + PETHREAD ThreadObject = NULL; + + if (NT_SUCCESS(ObReferenceObjectByHandle(ThreadHandle, THREAD_ALL_ACCESS, NULL, KernelMode, (PVOID*)(&ThreadObject), NULL))) + { + KeWaitForSingleObject((PVOID)(ThreadObject), Executive, KernelMode, FALSE, NULL); + ObDereferenceObject((PVOID)(ThreadObject)); + } + } +} + +BOOL IsAdressOutsideModulelist(uintptr_t address) +{ + BOOLEAN OutsideModulelist = TRUE; + PSYSTEM_MODULE_INFORMATION pSystemInfoBuffer; + ULONG SystemInfoBufferSize = 0; + + ZwQuerySystemInformation(SystemModuleInformation, 0, 0, &SystemInfoBufferSize); + + pSystemInfoBuffer = (PSYSTEM_MODULE_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, (SIZE_T)SystemInfoBufferSize * 2, AC_POOL_TAG); + + memset(pSystemInfoBuffer, 0, (SIZE_T)SystemInfoBufferSize * 2); + ZwQuerySystemInformation(SystemModuleInformation, pSystemInfoBuffer, (SIZE_T)SystemInfoBufferSize * 2, &SystemInfoBufferSize); + + for (ULONG l = 0; l < pSystemInfoBuffer->Count; l++) + { + if (address >= (ULONG64)pSystemInfoBuffer->Module[l].ImageBase && + address <= (ULONG64)pSystemInfoBuffer->Module[l].ImageBase + pSystemInfoBuffer->Module[l].ImageSize) + { + OutsideModulelist = FALSE; + break; + } + } + + return OutsideModulelist; +} + +BOOL DataCompare(const BYTE* pData, const BYTE* bMask, const char* szMask) +{ + for (; *szMask; ++szMask, ++pData, ++bMask) if (*szMask == 'x' && *pData != *bMask) return 0; + return (*szMask) == 0; +} + +UINT64 FindPattern(UINT64 dwAddress, UINT64 dwLen, BYTE* bMask, char* szMask) +{ + for (UINT64 i = 0; i < dwLen; i++) if (DataCompare((BYTE*)(dwAddress + i), bMask, szMask)) return (UINT64)(dwAddress + i); + return 0; +} + +PVOID ResolveRelativeAddress(PVOID Instruction, ULONG OffsetOffset, ULONG InstructionSize) +{ + ULONG_PTR Instr = (ULONG_PTR)Instruction; + LONG RipOffset = *(PLONG)(Instr + OffsetOffset); + PVOID ResolvedAddr = (PVOID)(Instr + InstructionSize + RipOffset); + + return ResolvedAddr; +} + +PVOID GetKernelBase(OUT PULONG pSize) +{ + NTSTATUS status = STATUS_SUCCESS; + ULONG bytes = 0; + PRTL_PROCESS_MODULES pMods = NULL; + PVOID checkPtr = NULL; + UNICODE_STRING routineName; + PVOID g_KernelBase = NULL; + ULONG g_KernelSize = 0; + + if (g_KernelBase != NULL) + { + if (pSize) + *pSize = g_KernelSize; + return g_KernelBase; + } + + RtlUnicodeStringInit(&routineName, L"NtOpenFile"); + + checkPtr = MmGetSystemRoutineAddress(&routineName); + if (checkPtr == NULL) + return NULL; + + status = ZwQuerySystemInformation(SystemModuleInformation, 0, bytes, &bytes); + if (bytes == 0) + { + return NULL; + } + + pMods = (PRTL_PROCESS_MODULES)ExAllocatePoolWithTag(NonPagedPool, bytes, AC_POOL_TAG); + + status = ZwQuerySystemInformation(SystemModuleInformation, pMods, bytes, &bytes); + + if (NT_SUCCESS(status)) + { + PRTL_PROCESS_MODULE_INFORMATION pMod = pMods->Modules; + + for (ULONG i = 0; i < pMods->NumberOfModules; i++) + { + if (checkPtr >= pMod[i].ImageBase && + checkPtr < (PVOID)((PUCHAR)pMod[i].ImageBase + pMod[i].ImageSize)) + { + g_KernelBase = pMod[i].ImageBase; + g_KernelSize = pMod[i].ImageSize; + if (pSize) + *pSize = g_KernelSize; + break; + } + } + } + + if (pMods) + ExFreePoolWithTag(pMods, AC_POOL_TAG); + + return g_KernelBase; +} + +VOID GetThreadStartAddress(PETHREAD ThreadObj, uintptr_t* pStartAddr) +{ + HANDLE hThread; + uintptr_t start_addr; + ULONG returned_bytes; + + if (!NT_SUCCESS(ObOpenObjectByPointer(ThreadObj, OBJ_KERNEL_HANDLE, NULL, GENERIC_READ, *PsThreadType, KernelMode, &hThread))) + return; + + if (!NT_SUCCESS(NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, &start_addr, sizeof(start_addr), &returned_bytes))) + { + NtClose(hThread); + return; + } + + if (!MmIsAddressValid((void*)start_addr)) + return; + + *pStartAddr = start_addr; + + NtClose(hThread); +} + +PSYSTEM_HANDLE_INFORMATION GetHandleList() +{ + ULONG neededSize = 8 * 1024 * 1024; + + PSYSTEM_HANDLE_INFORMATION pHandleList; + + pHandleList = (PSYSTEM_HANDLE_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, neededSize, AC_POOL_TAG); + ZwQuerySystemInformation(SystemHandleInformation, pHandleList, neededSize, 0); + return pHandleList; +} +``` \ No newline at end of file diff --git a/archive/gmh5225/NMI-nmi_callback.txt b/archive/gmh5225/NMI-nmi_callback.txt new file mode 100644 index 00000000..a56a1ec5 --- /dev/null +++ b/archive/gmh5225/NMI-nmi_callback.txt @@ -0,0 +1,551 @@ +Project Path: arc_gmh5225_NMI-nmi_callback_u7pp70ld + +Source Tree: + +```txt +arc_gmh5225_NMI-nmi_callback_u7pp70ld +├── README.md +├── demo.PNG +├── nmi_callback +│ ├── nmi_callback.inf +│ ├── nmi_callback.vcxproj +│ ├── nmi_callback.vcxproj.filters +│ └── src +│ ├── hal.c +│ ├── helper.c +│ ├── main.c +│ └── stuff.h +└── nmi_callback.sln + +``` + +`README.md`: + +```md +# nmi_callback +This driver will fire a non-maskable interrupt (NMI) on your 1st cpu core and attempt to handle it using an NMI callback. +If it succeeds, you will see following result in WinDbg: + +![alt text](https://github.com/cryotb/nmi_callback/blob/master/demo.PNG) + +In order to get this done, i am doing the following: + 1. Allocate KAFFINITY_EX structure. + 2. Pass it to KeInitializeAffinityEx. + 3. Add the CPU core the NMI should be run at to the affinity, by calling KeAddProcessorAffinityEx. + 4. Finally calling HalSendNMI to send it. parameter is the affinity of course. + +Note that all of these APIs listed above are not publicly documented so there might be inconsistencies on return value, prototype etc... + +``` + +`nmi_callback.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32510.428 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nmi_callback", "nmi_callback\nmi_callback.vcxproj", "{9180B6D2-48AE-42D1-A651-2324A16AF74A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|ARM.ActiveCfg = Debug|ARM + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|ARM.Build.0 = Debug|ARM + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|ARM.Deploy.0 = Debug|ARM + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|ARM64.Build.0 = Debug|ARM64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|x64.ActiveCfg = Debug|x64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|x64.Build.0 = Debug|x64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|x64.Deploy.0 = Debug|x64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|x86.ActiveCfg = Debug|Win32 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|x86.Build.0 = Debug|Win32 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Debug|x86.Deploy.0 = Debug|Win32 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|ARM.ActiveCfg = Release|ARM + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|ARM.Build.0 = Release|ARM + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|ARM.Deploy.0 = Release|ARM + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|ARM64.ActiveCfg = Release|ARM64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|ARM64.Build.0 = Release|ARM64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|ARM64.Deploy.0 = Release|ARM64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|x64.ActiveCfg = Release|x64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|x64.Build.0 = Release|x64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|x64.Deploy.0 = Release|x64 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|x86.ActiveCfg = Release|Win32 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|x86.Build.0 = Release|Win32 + {9180B6D2-48AE-42D1-A651-2324A16AF74A}.Release|x86.Deploy.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {052123F8-9C27-47D0-BD07-5857F363FB09} + EndGlobalSection +EndGlobal + +``` + +`nmi_callback/nmi_callback.inf`: + +```inf +; +; nmi_callback.inf +; + +[Version] +Signature="$WINDOWS NT$" +Class=Sample ; TODO: edit Class +ClassGuid={78A1C341-4539-11d3-B88D-00C04FAD5171} ; TODO: edit ClassGuid +Provider=%ManufacturerName% +CatalogFile=nmi_callback.cat +DriverVer= ; TODO: set DriverVer in stampinf property pages + +[DestinationDirs] +DefaultDestDir = 12 +nmi_callback_Device_CoInstaller_CopyFiles = 11 + +; ================= Class section ===================== + +[ClassInstall32] +Addreg=SampleClassReg + +[SampleClassReg] +HKR,,,0,%ClassName% +HKR,,Icon,,-5 + +[SourceDisksNames] +1 = %DiskName%,,,"" + +[SourceDisksFiles] +nmi_callback.sys = 1,, +WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll=1 ; make sure the number matches with SourceDisksNames + +;***************************************** +; Install Section +;***************************************** + +[Manufacturer] +%ManufacturerName%=Standard,NT$ARCH$ + +[Standard.NT$ARCH$] +%nmi_callback.DeviceDesc%=nmi_callback_Device, Root\nmi_callback ; TODO: edit hw-id + +[nmi_callback_Device.NT] +CopyFiles=Drivers_Dir + +[Drivers_Dir] +nmi_callback.sys + +;-------------- Service installation +[nmi_callback_Device.NT.Services] +AddService = nmi_callback,%SPSVCINST_ASSOCSERVICE%, nmi_callback_Service_Inst + +; -------------- nmi_callback driver install sections +[nmi_callback_Service_Inst] +DisplayName = %nmi_callback.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\nmi_callback.sys + +; +;--- nmi_callback_Device Coinstaller installation ------ +; + +[nmi_callback_Device.NT.CoInstallers] +AddReg=nmi_callback_Device_CoInstaller_AddReg +CopyFiles=nmi_callback_Device_CoInstaller_CopyFiles + +[nmi_callback_Device_CoInstaller_AddReg] +HKR,,CoInstallers32,0x00010000, "WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,WdfCoInstaller" + +[nmi_callback_Device_CoInstaller_CopyFiles] +WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll + +[nmi_callback_Device.NT.Wdf] +KmdfService = nmi_callback, nmi_callback_wdfsect +[nmi_callback_wdfsect] +KmdfLibraryVersion = $KMDFVERSION$ + +[Strings] +SPSVCINST_ASSOCSERVICE= 0x00000002 +ManufacturerName="" ;TODO: Replace with your manufacturer name +ClassName="Samples" ; TODO: edit ClassName +DiskName = "nmi_callback Installation Disk" +nmi_callback.DeviceDesc = "nmi_callback Device" +nmi_callback.SVCDESC = "nmi_callback Service" + +``` + +`nmi_callback/nmi_callback.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + {9180B6D2-48AE-42D1-A651-2324A16AF74A} + {1bc93793-694f-48fe-9372-81e2b05556fd} + v4.5 + 12.0 + Debug + Win32 + nmi_callback + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + false + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + false + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + Level3 + + + + + false + + + + + Level3 + + + + + false + Disabled + + + + + + + + + + + + + + + + + + + + +``` + +`nmi_callback/nmi_callback.vcxproj.filters`: + +```filters + + + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + + + Driver Files + + + + + + + + + + + +``` + +`nmi_callback/src/hal.c`: + +```c +#include "stuff.h" + +VOID HalSendNMI(PKAFFINITY_EX affinity) +{ + PVOID addr; + UNICODE_STRING routineName; + + RtlInitUnicodeString(&routineName, L"HalSendNMI"); + addr = MmGetSystemRoutineAddress(&routineName); + + if (!addr) __debugbreak(); + + ( (VOID(__fastcall*)(PKAFFINITY_EX))addr )(affinity); +} + +``` + +`nmi_callback/src/helper.c`: + +```c +#include "stuff.h" + +INT64 HLP_SecInNs(INT64 nSec) +{ + return (nSec * 10000000); +} + +VOID HLP_DelayExecutionThread(INT64 nSeconds) +{ + LARGE_INTEGER nDelay; + memset(&nDelay, 0, sizeof(nDelay)); + + nDelay.QuadPart -= HLP_SecInNs(nSeconds); + + KeDelayExecutionThread(KernelMode, FALSE, &nDelay); +} + +BOOLEAN HLP_FireNMI(INT core, PKAFFINITY_EX affinity) +{ + KeInitializeAffinityEx(affinity); + KeAddProcessorAffinityEx(affinity, core); + + HalSendNMI(affinity); + + return TRUE; +} + +``` + +`nmi_callback/src/main.c`: + +```c +#include "stuff.h" + +PVOID g_NmiCallbackHandle; +PKAFFINITY_EX g_NmiAffinity; +PNMI_CONTEXT g_NmiContext; + +BOOLEAN NmiCallback(PVOID context, BOOLEAN handled) +{ + PNMI_CONTEXT cpuContext = &((PNMI_CONTEXT)context)[KeGetCurrentProcessorNumberEx(NULL)]; + + ++cpuContext->NumFired; + + return TRUE; +} + +VOID DriverUnload(PDRIVER_OBJECT drvObj) +{ + DbgPrint("[nmi_cb] unload\n"); + + if (g_NmiCallbackHandle) KeDeregisterNmiCallback(g_NmiCallbackHandle); + if (g_NmiAffinity) ExFreePoolWithTag(g_NmiAffinity, NMI_CB_POOL_TAG); + if (g_NmiContext) ExFreePoolWithTag(g_NmiContext, NMI_CB_POOL_TAG); +} + +NTSTATUS DriverEntry(PDRIVER_OBJECT drvObj, PUNICODE_STRING regPath) +{ + DbgPrint("\n[nmi_cb] entry\n"); + + ULONG numCores = KeQueryActiveProcessorCountEx(NULL); + + drvObj->DriverUnload = DriverUnload; + + ULONG nmiContextLength = numCores * sizeof(NMI_CONTEXT); + + g_NmiAffinity = ExAllocatePoolWithTag(NonPagedPool, sizeof(KAFFINITY_EX), NMI_CB_POOL_TAG); + g_NmiContext = (PNMI_CONTEXT)ExAllocatePoolWithTag(NonPagedPool, nmiContextLength, NMI_CB_POOL_TAG); + g_NmiCallbackHandle = KeRegisterNmiCallback(NmiCallback, g_NmiContext); + + if (!g_NmiAffinity || !g_NmiContext || !g_NmiCallbackHandle) + return STATUS_FAILED_DRIVER_ENTRY; + + memset(g_NmiContext, 0, nmiContextLength); + + HLP_FireNMI(0, g_NmiAffinity); /* this will fire an NMI on CPU #0 */ + HLP_DelayExecutionThread(2); /* can adjust this delay, it's been arbitrarily chosen. */ + + for (ULONG i = 0; i < numCores; i++) + { + PNMI_CONTEXT cpuContext = &g_NmiContext[i]; + + DbgPrint("[nmi_cb] CPU#%i NmiCallbackInvokes=%i\n", i, cpuContext->NumFired); + } + + return STATUS_SUCCESS; +} + +``` + +`nmi_callback/src/stuff.h`: + +```h +#pragma once + +#include +#include + +#define NMI_CB_POOL_TAG 'BCmN' + +//0xa8 bytes (sizeof) +typedef struct _KAFFINITY_EX +{ + USHORT Count; //0x0 + USHORT Size; //0x2 + ULONG Reserved; //0x4 + ULONGLONG Bitmap[20]; //0x8 +} KAFFINITY_EX, *PKAFFINITY_EX; + +typedef struct _NMI_CONTEXT +{ + INT NumFired; +} NMI_CONTEXT, *PNMI_CONTEXT; + +EXTERN_C INT64 HLP_SecInNs(INT64 nSec); +EXTERN_C VOID HLP_DelayExecutionThread(INT64 nSeconds); +EXTERN_C BOOLEAN HLP_FireNMI(INT core, PKAFFINITY_EX affinity); + +EXTERN_C VOID KeInitializeAffinityEx(PKAFFINITY_EX affinity); +EXTERN_C VOID KeAddProcessorAffinityEx(PKAFFINITY_EX affinity, INT num); + +EXTERN_C VOID HalSendNMI(PKAFFINITY_EX affinity); + +``` \ No newline at end of file diff --git a/archive/gmh5225/NMICallbackBlocker2.txt b/archive/gmh5225/NMICallbackBlocker2.txt new file mode 100644 index 00000000..7172bd0e --- /dev/null +++ b/archive/gmh5225/NMICallbackBlocker2.txt @@ -0,0 +1,216 @@ +Project Path: arc_gmh5225_NMICallbackBlocker2_reb2phrj + +Source Tree: + +```txt +arc_gmh5225_NMICallbackBlocker2_reb2phrj +├── LICENSE +├── README.md +└── nmi.cpp + +``` + +`LICENSE`: + +``` +MIT License + +Copyright (c) 2024 ENV + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +``` + +`README.md`: + +```md +# NMICallbackBlocker2 +This is a POC Test project for INTEL CPUs on blocking NMI Entries through the IDT Handler. + +```cpp +typedef struct _KNMI_HANDLER_CALLBACK +{ + struct _KNMI_HANDLER_CALLBACK* Next; + void(*Callback)(); + void* Context; + void* Handle; +} KNMI_HANDLER_CALLBACK, * PKNMI_HANDLER_CALLBACK; + +typedef struct _KAFFINITY_EX +{ + USHORT Count; //0x0 + USHORT Size; //0x2 + ULONG Reserved; //0x4 + ULONGLONG Bitmap[20]; //0x8 +} KAFFINITY_EX, * PKAFFINITY_EX; + +void PreventNMIExecution() { + void* ntoskrnl_base = reinterpret_cast((uintptr_t)get_ntos_base_address()); + if (ntoskrnl_base == NULL) { + DbgPrint("[NMI] Failed to get ntoskrnl_base"); + } + + // Perform the pattern scanning to locate nmi_in_progress + char* pattern = _("\xE8\x00\x00\x00\x00\x83\xCB\xFF\x48\x8B\xD6"); + char* mask = _("x????xxxxxx"); + + char* NtoskrnlStr = _("ntoskrnl.exe"); + + void* ModuleSig = static_cast(FindPatternImage((PCHAR)GetKernelModuleBase(NtoskrnlStr), pattern, mask)); + QWORD pattern_idt = reinterpret_cast(ModuleSig); + // DbgPrint("[NMI] ModuleSig = %p\n", ModuleSig); + // DbgPrint("[NMI] patternIDT = %p\n", pattern_idt); + + if (pattern_idt != NULL) + { + pattern_idt = PswResolveRelativeAddress(pattern_idt, 1, 5); + pattern_idt = pattern_idt + 0x1a; + pattern_idt = PswResolveRelativeAddress(pattern_idt, 3, 7); + + *(QWORD*)(pattern_idt + 0x38) = *(QWORD*)(pattern_idt + 0x1A0); + *(QWORD*)(pattern_idt + 0x40) = *(QWORD*)(pattern_idt + 0x1A8); + // DbgPrint(_("[NMI] IDT Patched / NMIs Blocked")); + } +} +``` + +``` + +`nmi.cpp`: + +```cpp + +BOOL +CheckMask( + PCHAR Base, + PCHAR Pattern, + PCHAR Mask +) { + for (; *Mask; ++Base, ++Pattern, ++Mask) { + if (*Mask == 'x' && *Base != *Pattern) { + return FALSE; + } + } + + return TRUE; +} + +PVOID +FindPattern2( + PCHAR Base, + DWORD Length, + PCHAR Pattern, + PCHAR Mask +) { + Length -= (DWORD)strlen(Mask); + for (DWORD i = 0; i <= Length; ++i) { + PVOID Addr = &Base[i]; + if (CheckMask((PCHAR)Addr, Pattern, Mask)) { + return Addr; + } + } + + return 0; +} + +PVOID FindPatternImage( + PCHAR Base, + PCHAR Pattern, + PCHAR Mask +) { + PVOID Match = 0; + + PIMAGE_NT_HEADERS Headers = (PIMAGE_NT_HEADERS)(Base + ((PIMAGE_DOS_HEADER)Base)->e_lfanew); + PIMAGE_SECTION_HEADER Sections = IMAGE_FIRST_SECTION(Headers); + for (DWORD i = 0; i < Headers->FileHeader.NumberOfSections; ++i) { + PIMAGE_SECTION_HEADER Section = &Sections[i]; + if (*(PINT)Section->Name == 'EGAP' || memcmp(Section->Name, _(".text"), 5) == 0) { + Match = FindPattern2(Base + Section->VirtualAddress, Section->Misc.VirtualSize, Pattern, Mask); + if (Match) { + break; + } + } + } + + return Match; +} + +typedef struct _KNMI_HANDLER_CALLBACK +{ + struct _KNMI_HANDLER_CALLBACK* Next; + void(*Callback)(); + void* Context; + void* Handle; +} KNMI_HANDLER_CALLBACK, * PKNMI_HANDLER_CALLBACK; + +typedef struct _KAFFINITY_EX +{ + USHORT Count; //0x0 + USHORT Size; //0x2 + ULONG Reserved; //0x4 + ULONGLONG Bitmap[20]; //0x8 +} KAFFINITY_EX, * PKAFFINITY_EX; + +typedef ULONG KEPROCESSORINDEX; +extern "C" NTSYSAPI BOOLEAN NTAPI KeInterlockedSetProcessorAffinityEx(PKAFFINITY_EX pAffinity, KEPROCESSORINDEX idxProcessor); + + +QWORD PswResolveRelativeAddress( + QWORD Instruction, + DWORD OffsetOffset, + DWORD InstructionSize +) +{ + + QWORD Instr = (QWORD)Instruction; + INT32 RipOffset = *(INT32*)(Instr + OffsetOffset); + QWORD ResolvedAddr = (QWORD)(Instr + InstructionSize + RipOffset); + return ResolvedAddr; +} + +void PreventNMIExecution() { + void* ntoskrnl_base = reinterpret_cast((uintptr_t)get_ntos_base_address()); + if (ntoskrnl_base == NULL) { + DbgPrint("[NMI] Failed to get ntoskrnl_base"); + } + + // Perform the pattern scanning to locate nmi_in_progress + char* pattern = _("\xE8\x00\x00\x00\x00\x83\xCB\xFF\x48\x8B\xD6"); + char* mask = _("x????xxxxxx"); + + char* NtoskrnlStr = _("ntoskrnl.exe"); + + void* ModuleSig = static_cast(FindPatternImage((PCHAR)GetKernelModuleBase(NtoskrnlStr), pattern, mask)); + QWORD pattern_idt = reinterpret_cast(ModuleSig); + // DbgPrint("[NMI] ModuleSig = %p\n", ModuleSig); + // DbgPrint("[NMI] patternIDT = %p\n", pattern_idt); + + if (pattern_idt != NULL) + { + pattern_idt = PswResolveRelativeAddress(pattern_idt, 1, 5); + pattern_idt = pattern_idt + 0x1a; + pattern_idt = PswResolveRelativeAddress(pattern_idt, 3, 7); + + *(QWORD*)(pattern_idt + 0x38) = *(QWORD*)(pattern_idt + 0x1A0); + *(QWORD*)(pattern_idt + 0x40) = *(QWORD*)(pattern_idt + 0x1A8); + // DbgPrint(_("[NMI] IDT Patched / NMIs Blocked")); + } +} + +``` \ No newline at end of file diff --git a/archive/jlgreathouse/AMD_IBS_Toolkit.txt b/archive/jlgreathouse/AMD_IBS_Toolkit.txt new file mode 100644 index 00000000..841959d6 --- /dev/null +++ b/archive/jlgreathouse/AMD_IBS_Toolkit.txt @@ -0,0 +1,10526 @@ +Project Path: arc_jlgreathouse_AMD_IBS_Toolkit_jf1c5_ch + +Source Tree: + +```txt +arc_jlgreathouse_AMD_IBS_Toolkit_jf1c5_ch +├── CONTRIBUTORS.md +├── Makefile +├── README.md +├── README.txt +├── driver +│ ├── LICENSE +│ ├── Makefile +│ ├── ibs-core.c +│ ├── ibs-fops.c +│ ├── ibs-fops.h +│ ├── ibs-interrupt.c +│ ├── ibs-interrupt.h +│ ├── ibs-structs.h +│ ├── ibs-utils.c +│ ├── ibs-utils.h +│ ├── ibs-workarounds.c +│ ├── ibs-workarounds.h +│ ├── install_ibs_driver.sh +│ └── remove_ibs_driver.sh +├── ibs_with_perf_events.txt +├── include +│ ├── LICENSE.bsd +│ ├── LICENSE.gpl +│ ├── ibs-msr-index.h +│ └── ibs-uapi.h +├── lib +│ ├── LICENSE +│ ├── Makefile +│ ├── ibs.c +│ └── ibs.h +├── make +│ ├── LICENSE.bsd +│ ├── LICENSE.gpl +│ └── master.mk +├── prereqs +│ ├── LICENSE +│ ├── centos_7_install.sh +│ ├── opensuse_leap_42.2_install.sh +│ ├── ubuntu_14.04_install.sh +│ ├── ubuntu_16.04_install.sh +│ ├── ubuntu_17.10_install.sh +│ └── ubuntu_18.04_install.sh +└── tools + ├── LICENSE + ├── Makefile + ├── common.mk + ├── ibs_daemon + │ ├── Makefile + │ ├── ibs_daemon.c + │ └── run_ibs_daemon.sh + ├── ibs_decoder + │ ├── Makefile + │ ├── ibs_decoder.c + │ └── ibs_op_csv2Rdata.R + ├── ibs_monitor + │ ├── Makefile + │ ├── cpu_check.c + │ ├── cpu_check.h + │ ├── ibs_monitor.c + │ └── ibs_monitor.h + ├── ibs_run_and_annotate + │ ├── Makefile + │ └── ibs_run_and_annotate + └── ibs_test + ├── Makefile + ├── ibs_test.c + └── run_ibs_test.sh + +``` + +`CONTRIBUTORS.md`: + +```md +AMD Research IBS Toolkit Contributors +============================================ + +* **[Joseph Greathouse](https://github.com/jlgreathouse)** + * Project lead + * Current maintainer + +(Other Contributors Sorted Alphabetically) + +* **[Arkaprava Basu](https://github.com/ArkaBasu)** + * perf\_events analysis + +* **Jacob Combs** + * IBS kernel driver + * IBS monitor application + * IBS decoder application + +* **[Brian Kocoloski](https://github.com/briankoco)** + * libIBS + * IBS daemon + +* **[Shijia Wei](https://www.linkedin.com/in/shijia-wei-b40bb9a7)** + * Program counter annotation tool + +``` + +`Makefile`: + +``` +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The names of its contributors may not be used to endorse or +# promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. + +include make/master.mk + +BUILD_THESE:=$(DRIVER_DIR) $(LIB_DIR) $(TOOLS_DIR) + +TARGETS := all clean + +$(TARGETS): $(BUILD_THESE) + +$(TOOLS_DIR): $(LIB_DIR) + $(MAKE) -C $@ $(MAKECMDGOALS) + +$(LIB_DIR) $(DRIVER_DIR): + $(MAKE) -C $@ $(MAKECMDGOALS) + +.PHONY: $(TARGETS) $(BUILD_THESE) + +``` + +`README.md`: + +```md +AMD Research Instruction Based Sampling Toolkit +=============================================================================== + +This repository contains tools which can be used to access the Instruction Based Sampling (IBS) mechanism in AMD microprocessors from Families 10h, 12h, 15h, 16h, and 17h. IBS is a hardware mechanism that samples a subset of all of the instructions going through a processor. For each sampled instruction, a large amount of information is gathered and saved off as a program runs. + +This toolkit includes a Linux® kernel driver that helps gather these IBS samples, a user-level application to parse the raw binary dumped by the driver, and a helper application which will run other programs and collect IBS traces about them. + +This toolkit was written by AMD Research as a simplified way to gather IBS samples on a wide range of Linux systems. Newer Linux kernels (Beginning in the 3.2 timeframe) have support for IBS as part of the perf\_events system. This toolkit offers a simplified interface to the IBS system, but it also includes a set of directions ([ibs\_with\_perf\_events.txt](ibs_with_perf_events.txt)) for implementing the same functionality the "official" way. In essence, this toolkit may be useful for prototyping a system that uses IBS, which can later be ported to use perf\_events. + +Table of Contents +-------------------------------------------------------------------------------- + + * [AMD Research IBS Toolkit File Structure](#amd-research-ibs-toolkit-file-structure) + * [The AMD Research IBS Driver](#the-amd-research-ibs-driver) + * [A library to configure IBS and read IBS samples](#a-library-to-configure-ibs-and-read-ibs-samples) + * [A collection of user-level tools to gather and analyze IBS samples](#a-collection-of-user-level-tools-to-gather-and-analyze-ibs-samples) + * [An application that tests the IBS driver](#an-application-that-tests-the-ibs-driver) + * [An IBS monitoring program](#an-ibs-monitoring-program) + * [An application to decode binary IBS dumps](#an-application-to-decode-binary-ibs-dumps) + * [An application to match IBS samples with their instructions](#an-application-to-match-ibs-samples-with-their-instructions) + * [An application that uses the libIBS daemon](#an-application-that-uses-the-libibs-daemon) + * [Building and Installing the AMD Research IBS Driver and Toolkit](#building-and-installing-the-amd-research-ibs-driver-and-toolkit) + * [AMD Research IBS Toolkit Compatibility](#amd-research-ibs-toolkit-compatibility) + * [Using the AMD Research IBS Toolkit](#using-the-amd-research-ibs-toolkit) + * [Background on Instruction Based Sampling](#background-on-instruction-based-sampling) + * [Trademark Attribution](#trademark-attribution) + * [Background References](#background-references) + +AMD Research IBS Toolkit File Structure +-------------------------------------------------------------------------------- + +The AMD Research IBS Toolkit is split into three major pieces, each of which is licensed separately. These three pieces are: +### The AMD Research IBS Driver ### +* Located in [./driver/](driver) +* This is a Linux® kernel driver that allows access to IBS traces. +* It is licensed under [GPLv2](driver/LICENSE), with the same caveats as any other Linux kernel license. +* When installed, this will create two new devices per CPU core: + - /dev/cpu//ibs/fetch + - /dev/cpu//ibs/op +* These two devices can be read using poll and read commands. In addition, there are a number if ioctl commands that can be used to configure and query information about the devices. + - The structs used by reads, and the ioctl commands, are defined in: [./include/ibs-uapi.h](include/ibs-uapi.h) + - A list of bit value locations from the AMD manuals that describe individual entries in each IBS reading are contained in: [./include/ibs-msr-index.h](include/ibs-msr-index.h) + - These last two files are dual licensed. You can choose to use them under the [GPLv2](include/LICENSE.gpl) or under a [3-clause BSD license](include/LICENSE.bsd). + +### A library to configure IBS and read IBS samples ### +* Located in [./lib/](lib) +* It is licensed under a [3-clause BSD license](lib/LICENSE). +* This library allows user-level programs to easily configure the IBS driver. + - This includes enabling and disabling IBS, setting driver options such as internal IBS buffer sizes, and setting hardware configuration values. +* This library is also useful for reading IBS samples into meaningful data structures and making them available to other applications. +* This library also has a daemon mode, where a user program can launch an IBS-sample-reading daemon in the background that will dump IBS samples into a file while the regular program runs. + +### A collection of user-level tools to gather and analyze IBS samples ### +* Located in [./tools/](tools) +* All of this software is licensed under a [3-clause BSD license](tools/LICENSE). + +#### An application that tests the IBS driver #### +* Located in [./tools/ibs\_test/](tools/ibs_test) +* This application checks to see if the AMD Research IBS Driver is installed and configurable. It attempts to open the op sampling device and read samples. It does nothing with these samples. +* The application takes one argument: the number of times to attempt to read IBS samples from the driver before quitting. This is set by the optional argument to the application. + - 0 or a negative value for this means "run until killed". + +#### An IBS monitoring program #### +* Located in [./tools/ibs\_monitor/](tools/ibs_monitor) +* This application is a wrapper that enables IBS tracing in our driver, runs a target program, and saves off IBS traces into designated files until the target program ends. Afterwards, it disables IBS tracing. +* Essentially, this gathers IBS traces for other programs. + +#### An application to decode binary IBS dumps #### +* Located in [./tools/ibs\_decoder/](tools/ibs_decoder) +* By default, the ibs\_monitor application will dump full IBS traces directly to files without doing any decoding on them. This is to prevent the decoding work from interrupting or slowing down the application under test. +* The ibs\_decoder application will read in these binary traces that are essentially dumps of the IBS sample data structures and split them into easy-to-read CSV files. +* In addition, there is a script which will automatically convert these CSV files into R data structures, for further data analysis. + +#### An application to match IBS samples with their instructions #### +* Located in [./tools/ibs\_run\_and\_annotate/](tools/ibs_run_and_annotate) +* This application will run the IBS monitor and IBS decoder applications above on a target application. It will automatically run the target application, gather IBS traces, and decode them to a CSV file. +* In addition, it will save enough information about the program's dynamically linked libraries to allow nearly all IBS samples to be "annotated" with the instruction that they represent. If the libraries and target application are built with debug symbols, this tool will also annotate the IBS samples with the line of code that produced the sampled instruction. +* The end result of this run is a new annotated CSV file of IBS samples that also includes the source line of code, offset into the binary or library, AMD64 opcode of the instruction, and a human-readable version of the instruction. + +#### An application that uses the libIBS daemon #### +* Located in [./tools/ibs\_daemon/](tools/ibs_daemon) +* This is an example of how to use the [libIBS](lib) daemon to handle IBS sampling within an application. The daemon will start up another thread that will dump IBS traces to a file in a user-defined way. +* This application gathers a collection of op sample traces and dumps them to a small CSV file. It does this until the application ends. +* This is somewhat similar to what the ibs\_monitor application does, but this application demonstrates using the libIBS daemon and taking advantage of its ability to do user-defined handlers for IBS samples before spitting data out to a file. + +Building and Installing the AMD Research IBS Driver and Toolkit +-------------------------------------------------------------------------------- + +Everything in the AMD Research IBS Toolkit can be built from the main directory using the command: + + make + +This will build the driver, libIBS, and all of the tools. Alternately, it is also possible to go into each directory and use the `make` command to build only that tool. + +The make command uses the CC and CXX environment variables to find its compiler, and it uses the system-wide `cc` and `c++` compilers by default. You can override these to use other compilers (e.g. `clang`), by running e.g.: + + CC=clang CXX=clang++ make + +Note that this also allows Clang's scan-build by running: + + scan-build make + +In addition, compilation can be done in parallel with `make -j {parallelism #}` + +Finally, the `cppcheck` and `pylint` tools can be run on this repo with: + + make check + +Before using any IBS-using tools, you should install the IBS driver that you have built. There is a helper script in the ./driver/ directory for this: + + ./driver/install_ibs_driver.sh + +Note that, if you don't run this script with sudo, it will attempt to install the driver using a sudo command that will likely ask for your password. You may need to do this every time you boot the system, unless you add the ibs.ko module to your boot-time list of modules to load. + +After installing the driver, you should see IBS nodes in the file system at +the following locations for each core ID : + +1. /dev/cpu//ibs/op +2. /dev/cpu//ibs/fetch + + +To uninstall the IBS driver, you can either run: + + rmmod ibs + +Or you can use the helper script at: + + ./drivers/remove_ibs_driver.sh + +The user interface to the driver is documented in [./include/ibs-uapi.h](include/ibs-uapi.h). This file may be included by user application code. See [./tools/ibs\_monitor/](tools/ibs_monitor) for an example of how to interface with the driver. + +AMD Research IBS Toolkit Compatibility +-------------------------------------------------------------------------------- +This toolkit has been tested to compile and install on the following systems: +* CentOS 5.8 (Linux® kernel 2.6.18-419) + - Using gcc 4.1.2 +* CentOS 6.4 (Linux kernel 2.6.32-358.23.2) + - Using gcc 4.4.7, clang 3.4.2, cppcheck 1.63 +* CentOS 7.3 (Linux kernel 3.10.0-514.10.2) + - Using gcc 4.8.5, clang 3.4.2, cppcheck 1.75 +* OpenSUSE 11.2 (Linux kernel 2.6.31.14-0.8) + - Using gcc 4.4.1 +* OpenSUSE Leap 42.2 (Linux kernel 4.4.49-16) + - Using gcc 4.8.5, clang 3.8.0, cppcheck 1.70 +* Ubuntu 9.04 (Linux kernel 2.6.28-11) + - Using gcc 4.3.3 +* Ubuntu 10.04 LTS (Linux kernel 2.6.32-21) + - Using gcc 4.4.3 +* Ubuntu 12.04.5 LTS (Linux kernel 3.13.0-113) + - Using gcc 4.6.3, clang 3.0, cppcheck 1.52 +* Ubuntu 14.04.1 LTS (Linux kernel 3.19.0) + - Using gcc 4.8.2, clang 3.4, cppcheck 1.61, pylint 1.1.0 +* Ubuntu 14.04.4 LTS (Linux kernel 4.2.0-34) + - Using gcc 4.9.3, clang 3.5.0, cppcheck 1.61, pylint 1.1.0 +* Ubuntu 16.04.2 LTS (Linux kernel 4.4.0-66) + - Using gcc 5.4.0, clang 3.8.0, cppcheck 1.72, pylint 1.5.2 +* Ubuntu 16.10 (Linux kernel 4.8.0-22) + - Using gcc 6.2.0, clang 3.8.1, cppcheck 1.75 +* Ubuntu 18.04.1 LTS (Linux kernel 4.15.0-20) + - Using gcc 7.3.0, clang 6.0.0, cppcheck 1.81, pylint 1.8.3 + +In addition, it has been tested on the following processors, though its logic should work for any processors in AMD Families 10h, 12h, 14h, 15h, 16h, or 17h that support IBS: +* AMD Phenom™ II X4 B95 + - Family 10h Model 04h (Revision C) +* AMD Phenom™ II X6 1090T + - Family 10h Model 0Ah (Revision E) +* AMD Opteron™ 4274 HE + - Family 15h Model 01h (CPU formerly code-named "Bulldozer") +* AMD A8-5500 APU + - Family 15h Model 10h (CPU formerly code-named "Piledriver") +* AMD A10-7850K APU + - Family 15h Model 30h (CPU formerly code-named "Steamroller") +* AMD FX-8800P + - Family 15h Model 60h (CPU formerly code-named "Excavator") +* AMD Ryzen™ 7 1800X + - Family 17h Model 01h (CPU formerly code-named "Zen") +* AMD EPYC™ 7301 + - Family 17h Model 01h (CPU formerly code-named "Zen") +* AMD Ryzen 5 2400GE + - Family 17h Model 11h (CPU formerly code-named "Zen") +* AMD Ryzen 7 3700 X + - Family 17h Model 71h (CPU formerly code-named "Zen 2") + +Using the AMD Research IBS Toolkit +-------------------------------------------------------------------------------- +The AMD Research IBS Toolkit includes most of the tools necessary to analyze applications using IBS. This includes the driver to access IBS, a monitoring application which automatically gathers IBS samples from an application under test, an application to decode these IBS samples into a human-readable format, and a tool to annotate these samples with application-level information about each instruction. + +All of the directions here assume that the IBS driver, contained in [./driver/](driver), has been build and installed successfully. + +The simplest mechanism to access IBS traces is the IBS Monitor application in [./tools/ibs\_monitor/](tools/ibs_monitor). This application allows users to pass a target application to be studied. The application will be run with system-wide IBS samples enabled, and the monitor will continually gather these until the program ends. In order to decrease the noise caused by saving these traces out to the target files, the monitor stores IBS traces in a raw format -- basically dumping the data structure directly to file. + +After the trace has been gathered, the IBS decoder application can be used to decode these raw IBS traces into a human-readable CSV file. This application is found in [./tools/ibs\_decoder/](tools/ibs_decoder). This CSV file has one line per IBS sample, and each column describes one piece of information contained in that IBS sample. + +An example of how to run the IBS Monitor and Decoder is as follows. These commands assume you are in the ./tools/ directory. + +The following command will run the requested program with the given command line, and produce two IBS traces. One for Op samples (app.op) and one for Fetch samples (app.fetch). + + ./ibs_monitor/ibs_monitor -o app.op -f app.fetch ${program command line} + +The following command will then decode the two IBS traces and save them into their respective CSV files: + + ./ibs_decoder/ibs_decoder -i app.op -o op.csv -f app.fetch -g fetch.csv + +The follow command will run both of the above commands back-to-back and also annotate each IBS sample with information about the instruction that it sampled (such as its opcode and which line of code created it): + + ./tools/ibs_run_and_annotate/ibs_run_and_annotate -o -f -d ${output directory} -t ${temp directory} -w ${program working directory} -- ${program command line} + +Background on Instruction Based Sampling +-------------------------------------------------------------------------------- + +AMD Instruction Based Sampling (IBS) is a hardware performance monitoring mechanism that is available on AMD CPUs starting with the Family 10h generation of cores (e.g. processors code-named "Barcelona" and "Shanghai" and Phenom™ II branded consumer CPUs were from this generation). It is supported on AMD CPUs up through and including the current Family 17h processors (e.g. the Ryzen™ branded consumer CPUs) with various features in each generation. + +Traditionally, hardware performance counters increment whenever an event happens inside the CPU core. These events are counted whenever the core sees some event (such as a cache miss). This can lead to overcounting in cores that perform speculative, out-of-order execution, because the instruction that caused the event may never actually commit. + +A related limitation of traditional performance counters becomes apparent when performing sampling. Traditional performance counters allow the application to be interrupted whenever a performance counter rolls over from '-1' to '0'. This is often referred to as event-based sampling, since it samples (interrupts on) every Nth event [1], depending on the initial negative value in the counter. + +Event-based sampling allows developers to learn where in an applications events occur. However, out-of-order cores may not be able to precisely interrupt on the instruction that caused the Nth event (or, because of the reason mentioned above, may not even know which of many outstanding events is the Nth event). This produces a problem known as 'skid'. A developer that wants to know exactly which instruction causes an event will encounter many difficulties when using traditional performance counters in a speculative, out-of-order core [2]. + +AMD's solution to this problem is known as Instruction Based Sampling (IBS). In a nutshell, IBS tracks instructions rather than events (hence instruction-based sampling instead of event-based sampling). Every Nth instruction that goes through the core is 'marked'. As it flows through the pipeline, information about many events caused by that instruction are gathered. Then, when the instruction is completed, multiple pieces of information about that instruction's operation are available for logging [3, 4]. + +IBS on AMD processors is split into two parts: fetch sampling (front-end) and op sampling (back-end). AMD cores operate on AMD64/x86 instructions in the in-order front end of the processor. These are broken down into internal micro-operations for execution in the out-of-order back end of the processor. As such, IBS for front-end operations and IBS for back-end operations work in similar ways, but are completely separate from one another. + +Fetch (front-end) sampling counts the number of completed (successfully sent to the decoder) fetches. After observing N fetches (where N is a programmable number), the next fetch attempt is sampled. Information about that fetch operation is gathered. When the fetch operation is either sent to the decoder (i.e. it completes) or is aborted (e.g. due to a page fault), the processor is interrupted and the IBS information about the sampled fetch is made available to the OS through a series of model-specific registers (MSRs). + +Depending on the processor family, these Fetch IBS Samples can contain some or all of the following information: + +* Whether the fetch completed successfully (i.e. was sent to the decoder) +* The latency (in clock cycles) from the beginning to end of the fetch +* Whether the instruction hit or miss in the L1 and L2 instruction caches +* The size of the virtual memory page that the fetch accessed +* Whether the fetch hit or missed in the L1 and L2 TLBs +* The latency of the TLB refill if there was a TLB miss +* The virtual and physical addresses accessed by the fetch + +Op (back-end) sampling can be configured to count either the number of clock cycles or the number of dispatched micro-ops. In either case, once the programmable number of counts has taken place, the next micro-op is tagged. As that micro-op flows through the out-of-order back end of the processor, information about the events it causes are stored. When the op is retired, the processor is interrupted and the IBS information about the sampled op is made available to the OS through a series of MSRs. + +Depending on the process family, these Op IBS Samples can contain some or all of the following information: + +* The virtual address of the instruction associated with this micro-op +* The number of cycles between completion of the op's work and its retirement +* The number of cycles between tagging the micro-op and its retirement +* Whether the op was a return, resync, or mispredicted return +* Whether the op was a branch and/or fused branch and whether it was mispredicted and/or taken +* The target address of any branch op +* Whether the op was from a micro-coded instruction +* If the op was a load/store, and whether it hit in the cache +* Whether a load/store op hit in the L1 or L2 data caches +* Whether a load/store op hit in the L1 or L2 TLBs and/or the size of the page +* The source (e.g. DRAM, NUMA Node) of any data returned to a memory op from the north bridge +* The width of the op's memory access +* Whether the op was a software prefetch +* The latency of any cache or TLB refill +* The number of outstanding memory accesses when a load's value is returned +* The virtual and physical addresses accessed by any load or store + +For more information about the technical details of AMD's Instruction Based Sampling, please refer AMD's various processor manuals: [5-17] + +For more information about micro-ops in AMD cores, please refer to AMD's software optimization guides: [5-6, 18-19]. In particular, note that some of the descriptions in these manuals refer to macro-ops and micro-ops. For instance, in Family 17h cores, AMD64 instructions are broken into one or more macro-ops. These macro-ops are dispatched into the back-end of the pipeline, where they may be split into one or two micro-ops. For instane, an instruction that needs both the ALU (to do math or logic operatinos) and AGU (to calculate an address for a load or a store) will be split into two micro-ops. One of those micro-ops will go into the ALU scheduler units and the other will go to the AGU scheduler units. In these Family 17h cores, IBS op sampling actually samples macro-ops at dispatch time. + +### Background References ### +1. S. V. Moore, "[A Comparison of Counting and Sampling Modes of Using Performance Monitoring Hardware](http://icl.cs.utk.edu/news_pub/submissions/icl-ut-02-01_perfmodels.pdf)," in Proc. of the Int'l Conf. on Computational Science-Part II (ICCS), 2002. +2. J. Dean, J. Hicks, C. A. Waldspurger, W. E. Weihl, G. Chrysos, "[ProfileMe: Hardware Support for Instruction-Level Profiling on Out-of-Order Processors](https://doi.org/10.1109/MICRO.1997.645821)," in Proc. of the 30th IEEE/ACM Int'l Symp. on Microarchitecture (MICRO-30), 1997. +3. P. J. Drongowski, "[Instruction-Based Sampling: A New Performance Analysis Technique for AMD Family 10h Processors](http://developer.amd.com/wordpress/media/2012/10/AMD_IBS_paper_EN.pdf)," AMD Technical Report, 2007. +4. P. Drongowski, L. Yu, F. Swehosky, S. Suthikulpanit, R. Richter, "[Incorporating Instruction-Based Sampling into AMD CodeAnalyst](https://doi.org/10.1109/ISPASS.2010.5452049)," in Proc. of the 2010 IEEE Int'l Symp. on Performance Analysis of Systems & Software (ISPASS), 2010. +5. Advanced Micro Devices, Inc. "[Software Optimization Guide for AMD Family 10h and 12h Processors](http://support.amd.com/techdocs/40546.pdf)". AMD Publication #40546. Rev. 3.13. Appendix G. +6. Advanced Micro Devices, Inc. "[Software Optimization Guide for AMD Family 15h Processors](https://support.amd.com/TechDocs/47414_15h_sw_opt_guide.pdf)". AMD Publication #47414. Rev. 3.07. Appendix F. +7. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 10h Processors](https://developer.amd.com/wordpress/media/2012/10/31116.pdf)". AMD Publication #31116. Rev. 3.62. +8. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 12h Processors](https://support.amd.com/TechDocs/41131.pdf)". AMD Publication #41131. Rev. 3.03. +9. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 14h Models 00h-0Fh Processors](http://support.amd.com/TechDocs/43170_14h_Mod_00h-0Fh_BKDG.pdf)". AMD Publication #43170. Rev. 3.03. +10. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 15h Models 00h-0Fh Processors](http://support.amd.com/TechDocs/42301_15h_Mod_00h-0Fh_BKDG.pdf)". AMD Publication #42301. Rev. 3.14. +11. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 15h Models 10h-1Fh Processors](http://support.amd.com/TechDocs/42300_15h_Mod_10h-1Fh_BKDG.pdf)". AMD Publication #42300. Rev. 3.12. +12. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 15h Models 30h-3Fh Processors](https://support.amd.com/TechDocs/49125_15h_Models_30h-3Fh_BKDG.pdf)". AMD Publication #49125. Rev. 3.06. +13. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 15h Models 60h-6Fh Processors](http://support.amd.com/TechDocs/50742_15h_Models_60h-6Fh_BKDG.pdf)". AMD Publication #50742. Rev. 3.05. +14. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 15h Models 70h-7Fh Processorsi](http://support.amd.com/TechDocs/55072_AMD_Family_15h_Models_70h-7Fh_BKDG.pdf)". AMD Publication #55072. Rev. 3.00. +15. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 16h Models 00h-0Fh Processors](http://support.amd.com/TechDocs/48751_16h_bkdg.pdf)". AMD Publication #48751. Rev. 3.03. +16. Advanced Micro Devices, Inc. "[BIOS and Kernel Developer's Guide (BKDG) For AMD Family 16h Models 30h-3Fh Processors](http://support.amd.com/TechDocs/52740_16h_Models_30h-3Fh_BKDG.pdf)". AMD Publication #52740. Rev. 3.06. +17. Advanced Micro Devices, Inc. "[Processor Programming Reference (PPR) for AMD Family 17h Model 01h, Revision B1 Processors](http://support.amd.com/TechDocs/54945_PPR_Family_17h_Models_00h-0Fh.pdf)". AMD Publication #54945. +18. Advanced Micro Devices, Inc. "[Software Optimization Guide for AMD Family 16h Processors](http://support.amd.com/TechDocs/52128_16h_Software_Opt_Guide.zip)". AMD Publication #52128. Rev. 1.1. +19. Advanced Micro Devices, Inc. "[Software Optimization Guide for AMD Family 17h Processors](https://developer.amd.com/wordpress/media/2013/12/55723_3_00.ZIP)". AMD Publication #55723. Rev. 3.00. + + +Trademark Attribution +-------------------------------------------------------------------------------- +© 2017-2019 Advanced Micro Devices, Inc. All rights reserved. AMD, the AMD Arrow logo, AMD Phenom, Opteron, Ryzen, EPYC, and combinations thereof are trademarks of Advanced Micro Devices, Inc. in the United States and/or other jurisdictions. Linux is a registered trademark of Linus Torvalds. Other names are for informational purposes only and may be trademarks of their +respective owners. + +``` + +`README.txt`: + +```txt +AMD Research Instruction Based Sampling Toolkit +=============================================================================== + +This repository contains tools which can be used to access the Instruction +Based Sampling (IBS) mechanism in AMD microprocessors from Families 10h, 12h, +15h, 16h, and 17h. IBS is a hardware mechanism that samples a subset of all of +the instructions going through a processor. For each sampled instruction, a +large amount of information is gathered and saved off as a program runs. + +This toolkit includes a Linux(r) kernel driver that helps gather these IBS +samples, a user-level application to parse the raw binary dumped by the driver, +and a helper application which will run other programs and collect IBS +traces about them. + +This toolkit was written by AMD Research as a simplified way to gather IBS +samples on a wide range of Linux systems. Newer Linux kernels (Beginning in the +3.2 timeframe) have support for IBS as part of the perf_events system. +This toolkit offers a simplified interface to the IBS system, but it also +includes a set of directions (ibs_with_perf_events.txt) for implementing the +same functionality the "official" way. In essence, this toolkit may be useful +for prototyping a system that uses IBS, which can later be ported to use +perf_events. + + + +AMD Research IBS Toolkit File Structure +-------------------------------------------------------------------------------- + +The AMD Research IBS Toolkit is split into three major pieces, each of which +is licensed separately. These three pieces are: +* The AMD Research IBS Driver located in ./driver/ + - This is a Linux(r) kernel driver that allows access to IBS traces. + - It is licensed under GPLv2, with the same caveats as any other Linux + kernel license. + - When installed, this will create two new devices per CPU core: + 1. /dev/cpu//ibs/fetch + 2. /dev/cpu//ibs/op + - These two devices can be read using poll and read commands + In addition, there are a number if ioctl commands that can be used to + configure and query information about the devices. + - The structs used by reads, and the ioctl commands, are defined in: + ./include/ibs-uapi.h + A list of bit value locations from the AMD manuals that describe + individual entries in each IBS reading are contained in: + ./include/ibs-msr-index.h + These last two files are dual licensed. You can choose to use them + under the GPLv2 or under a 3-clause BSD license. +* A library to configure IBS and read IBS samples located in ./lib/ + - This library allows user-level programs to easily configure the IBS + driver. This includes enabling and disabling IBS, setting driver + options such as internal IBS buffer sizes, and setting hardware + configuration values. + - This library is also useful for reading IBS samples into meaningful + data structures and making them available to other applications. + - This library also has a daemon mode, where a user program can launch + an IBS-sample-reading daemon in the background that will dump IBS + samples into a file while the regular program runs. + - It is licensed under a 3-clause BSD license. +* A collection of user tools to gather and analyze IBS samples in ./tools/ + All of this software is licensed under a 3-clause BSD license + This collection of tools includes: + * An application that tests the IBS driver, located in ./tools/ibs_test/ + - This application checks to see if the AMD Research IBS Driver is + installed and configurable. It attempts to open the op sampling + device and read samples. It does nothing with these samples. + - The application takes one argument: the number of times to attempt + to read IBS samples from the driver before quitting. This is set + by the optional argument to the application. + 0 or a negative value for this means "run until killed". + * An IBS monitoring program located in ./tools/ibs_monitor/ + - This application is a wrapper that enables IBS tracing in our driver, + runs a target program, and saves off IBS traces into designated files + until the target program ends. Afterwards, it disables IBS tracing. + - Essentially, this gathers IBS traces for other programs. + * An application to decode binary IBS dumps in ./tools/ibs_decoder/ + - By default, the ibs_monitor application will dump full IBS traces + directly to files without doing any decoding on them. This is to + prevent the decoding work from interrupting or slowing down the + application under test. + - The ibs_decoder application will read in these binary traces that + are essentially dumps of the IBS sample data structures and split + them into easy-to-read CSV files. + - In addition, there is a script which will automatically convert + these CSV files into R data structures, for further data analysis. + * An application to match IBS samples with their instructions in + ./tools/ibs_run_and_annotate/ + - This application will run the IBS monitor and IBS decoder + applications above on a target application. It will automatically + run the target application, gather IBS traces, and decode them to + a CSV file. + - In addition, it will save enough information about the program's + dynamically linked libraries to allow nearly all IBS samples to be + "annotated" with the instruction that they represent. If the + libraries and target application are built with debug symbols, this + tool will also annotate the IBS samples with the line of code that + produced the sampled instruction. + - The end result of this run is a new annotated CSV file of IBS samples + that also includes the source line of code, offset into the binary + or library, AMD64 opcode of the instruction, and a human-readable + version of the instruction. + * An application that uses the LibIBS daemon in ./tools/ibs_daemon/ + - This is an example of how to use the LibIBS daemon to handle IBS + sampling within an application. The daemon will start up another + thread that will dump IBS traces to a file in a user-defined + way. + - This application gathers a collection of op sample traces and dumps + them to a small CSV file. It does this until the application ends. + - This is somewhat similar to what the ibs_monitor application does, + but this application demonstrates using the libIBS daemon and + taking advantage of its ability to do user-defined handlers for + IBS samples before spitting data out to a file. + + + +Building and Installing the AMD Research IBS Driver and Toolkit +-------------------------------------------------------------------------------- + +Everything in the AMD Research IBS Toolkit can be built from the main directory +using the command: + make + +This will build the driver, libIBS, and all of the tools. Alternately, it is +also possible to go into each directory and use the 'make' command to build +only that tool. + +The make command uses the CC and CXX environment variables to find its +compiler, and it uses the system-wide 'cc' and 'c++' compilers by default. +You can override these to use other compilers (e.g. clang), by running e.g.: + CC=clang CXX=clang++ make + +Note that this also allows Clang's scan-build by running: + scan-build make + +In addition, compilation can be done in parallel with 'make -j {parallelism#}' + +Finally, the 'cppcheck' and 'pylint' tools can be run on this repo with: + make check + + + +Before using any IBS-using tools, you should install the IBS driver that you +have built. There is a helper script in the ./driver/ directory for this: + ./driver/install_ibs_driver.sh + +Note that, if you don't run this script with sudo, it will attempt to install +the driver using a sudo command that will likely ask for your password. +You may need to do this every time you boot the system, unless you add the +ibs.ko module to your boot-time list of modules to load. + +After installing the driver, you should see IBS nodes in the file system at +the following locations for each core ID : + /dev/cpu//ibs/op + /dev/cpu//ibs/fetch + + +To uninstall the IBS driver, you can either run: + rmmod ibs +Or you can use the helper script at: + ./drivers/remove_ibs_driver.sh + + + +The user interface to the driver is documented in ./include/ibs-uapi.h. +This file may be included by user application code. See ./tools/ibs_monitor/ +for an example of how to interface with the driver. + + +AMD Research IBS Toolkit Compatibility +-------------------------------------------------------------------------------- +This toolkit has been tested to compile and install on the following systems: + * CentOS 5.8 (Linux(r) kernel 2.6.18-419) + Using gcc 4.1.2 + * CentOS 6.4 (Linux kernel 2.6.32-358.23.2) + Using gcc 4.4.7, clang 3.4.2, cppcheck 1.63 + * CentOS 7.3 (Linux kernel 3.10.0-514.10.2) + Using gcc 4.8.5, clang 3.4.2, cppcheck 1.75 + * OpenSUSE 11.2 (Linux kernel 2.6.31.14-0.8) + Using gcc 4.4.1 + * OpenSUSE Leap 42.2 (Linux kernel 4.4.49-16) + Using gcc 4.8.5, clang 3.8.0, cppcheck 1.70 + * Ubuntu 9.04 (Linux kernel 2.6.28-11) + Using gcc 4.3.3 + * Ubuntu 10.04 LTS (Linux kernel 2.6.32-21) + Using gcc 4.4.3 + * Ubuntu 12.04.5 LTS (Linux kernel 3.13.0-113) + Using gcc 4.6.3, clang 3.0, cppcheck 1.52 + * Ubuntu 14.04.1 LTS (Linux kernel 3.19.0) + Using gcc 4.8.2, clang 3.4, cppcheck 1.61, pylint 1.1.0 + * Ubuntu 14.04.4 LTS (Linux kernel 4.2.0-34) + Using gcc 4.9.3, clang 3.5.0, cppcheck 1.61, pylint 1.1.0 + * Ubuntu 16.04.2 LTS (Linux kernel 4.4.0-66) + Using gcc 5.4.0, clang 3.8.0, cppcheck 1.72, pylint 1.5.2 + * Ubuntu 16.10 (Linux kernel 4.8.0-22) + Using gcc 6.2.0, clang 3.8.1, cppcheck 1.75 + * Ubuntu 18.04.1 LTS (Linux kernel 4.15.0-20) + Using gcc 7.3.0, clang 6.0.0, cppcheck 1.81, pylint 1.8.3 + +In addition, it has been tested on the following processors, though its logic +should work for any processors in AMD Families 10h, 12h, 14h, 15h, 16h, or 17h +that support IBS: + * AMD Phenom(tm) II X4 B95 + Family 10h Model 04h (Revision C) + * AMD Phenom(tm) II X6 1090T + Family 10h Model 0Ah (Revision E) + * AMD Opteron(tm) 4274 HE + Family 15h Model 01h (CPU formerly code-named "Bulldozer") + * AMD A8-5500 APU + Family 15h Model 10h (CPU formerly code-named "Piledriver") + * AMD A10-7850K APU + Family 15h Model 30h (CPU formerly code-named "Steamroller") + * AMD FX-8800P + Family 15h Model 60h (CPU formerly code-named "Excavator") + * AMD Ryzen(tm) 7 1800X + Family 17h Model 01h (CPU formerly code-named "Zen") + * AMD EPYC(tm) 7301 + Family 17h Model 01h (CPU formerly code-named "Zen") + * AMD Ryzen 5 2400GE + Family 17h Model 11h (CPU formerly code-named "Zen") + * AMD Ryzen 7 3700X + Family 17h Model 71h (CPU formerly code-named "Zen 2") + +Using the AMD Research IBS Toolkit +-------------------------------------------------------------------------------- +The AMD Research IBS Toolkit includes most of the tools necessary to analyze +applications using IBS. This includes the driver to access IBS, a monitoring +application which automatically gathers IBS samples from an application under +test, an application to decode these IBS samples into a human-readable format, +and a tool to annotate these samples with application-level information about +each instruction. + +All of the directions here assume that the IBS driver, contained in ./driver/, +has been build and installed successfully. + +The simplest mechanism to access IBS traces is the IBS Monitor application in +./tools/ibs_monitor/. This application allows users to pass a target +application to be studied. The application will be run with system-wide IBS +samples enabled, and the monitor will continually gather these until the +program ends. In order to decrease the noise caused by saving these traces out +to the target files, the monitor stores IBS traces in a raw format -- basically +dumping the data structure directly to file. + +After the trace has been gathered, the IBS decoder application can be used to +decode these raw IBS traces into a human-readable CSV file. This application +is found in ./tools/ibs_decoder/. This CSV file has one line per IBS sample, +and each column describes one piece of information contained in that IBS +sample. + +An example of how to run the IBS Monitor and Decoder is as follows. These +commands assume you are in the ./tools/ directory. + +The following command will run the requested program with the given command +line, and produce two IBS traces. One for Op samples (app.op) and one for +Fetch samples (app.fetch). + ./ibs_monitor/ibs_monitor -o app.op -f app.fetch ${program command line} + +The following command will then decode the two IBS traces and save them +into their respective CSV files: + ./ibs_decoder/ibs_decoder -i app.op -o op.csv -f app.fetch -g fetch.csv + +The follow command will run both of the above commands back-to-back and also +annotate each IBS sample with information about the instruction that it +sampled (such as its opcode and which line of code created it): + ./tools/ibs_run_and_annotate/ibs_run_and_annotate -o -f -d ${out_dir} \ + -w ${working_dir} -- ${program command line} + +TODO: + 1. We should add an application that wraps malloc(), free(), etc. This will + allow us to map heap memory regions to the line of code that generated + them. In the end, this will allow us to understand which data structures + are accessed frequently, based on the IBS load/store address information. + 2. After doing #1, we need a script that automatically scans the human- + readable CSV files and adds in the memory region information. + + +Background on Instruction Based Sampling +-------------------------------------------------------------------------------- + +AMD Instruction Based Sampling (IBS) is a hardware performance monitoring +mechanism that is available on AMD CPUs starting with the Family 10h generation +of cores (e.g. processors code-named "Barcelona" and "Shanghai" and Phenom- +branded consumer CPUs were from this generation). It is supported on AMD CPUs +up through and including the current Family 17h processors (e.g. the +Ryzen-branded consumer CPUs) with various features in each generation. + +Traditionally, hardware performance counters increment whenever an event +happens inside the CPU core. These events are counted whenever the core sees +some event (such as a cache miss). This can lead to overcounting in cores that +perform speculative, out-of-order execution, because the instruction that +caused the event may never actually commit. + +A related limitation of traditional performance counters becomes apparent when +performing sampling. Traditional performance counters allow the application +to be interrupted whenever a performance counter rolls over from '-1' to '0'. +This is often referred to as event-based sampling, since it samples (interrupts +on) every Nth event [1], depending on the initial negative value in the counter. + +Event-based sampling allows developers to learn where in an applications events +occur. However, out-of-order cores may not be able to precisely interrupt on +the instruction that caused the Nth event (or, because of the reason mentioned +above, may not even know which of many outstanding events is the Nth event). +This produces a problem known as 'skid'. A developer that wants to know exactly +which instruction causes an event will encounter many difficulties when using +traditional performance counters in a speculative, out-of-order core [2]. + +AMD's solution to this problem is known as Instruction Based Sampling (IBS). +In a nutshell, IBS tracks instructions rather than events (hence instruction- +based sampling instead of event-based sampling). Every Nth instruction that +goes through the core is 'marked'. As it flows through the pipeline, +information about many events caused by that instruction are gathered. Then, +when the instruction is completed, multiple pieces of information about that +instruction's operation are available for logging [3, 4]. + +IBS on AMD processors is split into two parts: fetch sampling (front-end) and +op sampling (back-end). AMD cores operate on AMD64/x86 instructions in the +in-order front end of the processor. These are broken down into internal +micro-operations for execution in the out-of-order back end of the processor. +As such, IBS for front-end operations and IBS for back-end operations work +in similar ways, but are completely separate from one another. + + + +Fetch (front-end) sampling counts the number of completed (successfully sent +to the decoder) fetches. After observing N fetches (where N is a programmable +number), the next fetch attempt is sampled. Information about that fetch +operation is gathered. When the fetch operation is either sent to the decoder +(i.e. it completes) or is aborted (e.g. due to a page fault), the processor +is interrupted and the IBS information about the sampled fetch is made +available to the OS through a series of model-specific registers (MSRs). + +Depending on the processor family, these Fetch IBS Samples can contain +some or all of the following information: + +* Whether the fetch completed successfully (i.e. was sent to the decoder) +* The latency (in clock cycles) from the beginning to end of the fetch +* Whether the instruction hit or miss in the L1 and L2 instruction caches +* The size of the virtual memory page that the fetch accessed +* Whether the fetch hit or missed in the L1 and L2 TLBs +* The latency of the TLB refill if there was a TLB miss +* The virtual and physical addresses accessed by the fetch + + + +Op (back-end) sampling can be configured to count either the number of clock +cycles or the number of dispatched micro-ops. In either case, once the +programmable number of counts has taken place, the next micro-op is tagged. +As that micro-op flows through the out-of-order back end of the processor, +information about the events it causes are stored. When the op is retired, +the processor is interrupted and the IBS information about the sampled +op is made available to the OS through a series of MSRs + +Depending on the process family, these Op IBS Samples can contain some or all +of the following information: + +* The virtual address of the instruction associated with this micro-op +* The number of cycles between completion of the op's work and its retirement +* The number of cycles between tagging the micro-op and its retirement +* Whether the op was a return, resync, or mispredicted return +* Whether the op was a branch and/or fused branch and whether it was + mispredicted and/or taken +* The target address of any branch op +* Whether the op was from a micro-coded instruction +* If the op was a load/store, and whether it hit in the cache +* Whether a load/store op hit in the L1 or L2 data caches +* Whether a load/store op hit in the L1 or L2 TLBs and/or the size of the page +* The source (e.g. DRAM, NUMA Node) of any data returned to a memory op from + the north bridge +* The width of the op's memory access +* Whether the op was a software prefetch +* The latency of any cache or TLB refill +* The number of outstanding memory accesses when a load's value is returned +* The virtual and physical addresses accessed by any load or store + + + +For more information about the technical details of AMD's Instruction Based +Sampling, please refer AMD's various processor manuals: [5-17] + +For more information about micro-ops in AMD cores, please refer to AMD's +software optimization guides: [5-6, 18-19]. In particular, note that some of +the descriptions in these manuals refer to macro-ops and micro-ops. For +instance, in Family 17h cores, AMD64 instructions are broken into one or more +macro-ops. These macro-ops are dispatched into the back-end of the pipeline, +where they may be split into one or two micro-ops. For instane, an instruction +that needs both the ALU (to do math or logic operatinos) and AGU (to calculate +an address for a load or a store) will be split into two micro-ops. One of +those micro-ops will go into the ALU scheduler units and the other will go to +the AGU scheduler units. In these Family 17h cores, IBS op sampling actually +samples macro-ops at dispatch time. + + +[1] S. V. Moore, "A Comparison of Counting and Sampling Modes of Using + Performance Monitoring Hardware," in Proc. of the Int'l Conf. on + Computational Science-Part II (ICCS), 2002. +[2] J. Dean, J. Hicks, C. A. Waldspurger, W. E. Weihl, G. Chrysos, + "ProfileMe: Hardware Support for Instruction-Level Profiling on + Out-of-Order Processors," in Proc. of the 30th IEEE/ACM Int'l Symp. on + Microarchitecture (MICRO-30), 1997. +[3] P. J. Drongowski, "Instruction-Based Sampling: A New Performance Analysis + Technique for AMD Family 10h Processors," AMD Technical Report, 2007. +[4] P. Drongowski, L. Yu, F. Swehosky, S. Suthikulpanit, R. Richter, + "Incorporating Instruction-Based Sampling into AMD CodeAnalyst," in Proc. + of the 2010 IEEE Int'l Symp. on Performance Analysis of Systems & Software + (ISPASS), 2010. +[5] Advanced Micro Devices, Inc. "Software Optimization Guide for AMD Family + 10h and 12h Processors". AMD Publication #40546. Rev. 3.13. Appendix G. +[6] Advanced Micro Devices, Inc. "Software Optimization Guide for AMD Family + 15h Processors". AMD Publication #47414. Rev. 3.07. Appendix F. +[7] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 10h Processors". AMD Publication #31116. Rev. 3.62. +[8] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 12h Processors". AMD Publication #41131. Rev. 3.03. +[9] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 14h Models 00h-0Fh Processors". AMD Publication #43170. + Rev. 3.03. +[10] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 15h Models 00h-0Fh Processors". AMD Publication #42301. + Rev. 3.14. +[11] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 15h Models 10h-1Fh Processors". AMD Publication #42300. + Rev. 3.12. +[12] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 15h Models 30h-3Fh Processors". AMD Publication #49125. + Rev. 3.06. +[13] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 15h Models 60h-6Fh Processors". AMD Publication #50742. + Rev. 3.05. +[14] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 15h Models 70h-7Fh Processors". AMD Publication #55072. + Rev. 3.00. +[15] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 16h Models 00h-0Fh Processors". AMD Publication #48751. + Rev. 3.03. +[16] Advanced Micro Devices, Inc. "BIOS and Kernel Developer's Guide (BKDG) For + AMD Family 16h Models 30h-3Fh Processors". AMD Publication #52740. + Rev. 3.06. +[17] Advanced Micro Devices, Inc. "Processor Programming Reference (PPR) for + AMD Family 17h Model 01h, Revision B1 Processors". AMD Publication #54945. +18. Advanced Micro Devices, Inc. "Software Optimization Guide for AMD Family + 16h Processors. AMD Publication #52128. Rev. 1.1. +19. Advanced Micro Devices, Inc. "Software Optimization Guide for AMD Family + 17h Processors. AMD Publication #55723. Rev. 3.00. + + +Trademark Attribution +-------------------------------------------------------------------------------- +(c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. +AMD, the AMD Arrow logo, AMD Phenom, Opteron, Ryzen, EPYC, and combinations +thereof are trademarks of Advanced Micro Devices, Inc. in the United States +and/or other jurisdictions. Linux is a registered trademark of Linus Torvalds. +Other names are for informational purposes only and may be trademarks of their +respective owners. + +``` + +`driver/LICENSE`: + +``` + + NOTE! This copyright does *not* cover user programs that use kernel + services by normal system calls - this is merely considered normal use + of the kernel, and does *not* fall under the heading of "derived work". + Also note that the GPL below is copyrighted by the Free Software + Foundation, but the instance of code that it refers to (the linux + kernel) is copyrighted by me and others who actually wrote it. + + Linus Torvalds + +---------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +``` + +`driver/Makefile`: + +``` +# Copyright (c) 2015-2018 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under the Linux kernel's version of the GPLv2 +# See driver/LICENSE for more licensing details. + +ifneq ($(KERNELRELEASE),) + +obj-m := ibs.o +ibs-y := ibs-core.o ibs-fops.o ibs-interrupt.o ibs-utils.o ibs-workarounds.o + +EXTRA_CFLAGS += $(CFLAGS) + +else + +SKIP_PEDANTIC=1 +SKIP_WEXTRA=1 +include ../make/master.mk + +BUILD_THESE=$(DRIVER_DIR) + +KDIR ?= /lib/modules/$(shell uname -r)/build +CFLAGS+=-I$(CURDIR)/$(INCLUDE_DIR) + +all: +ifneq (,$(findstring ccc-analyzer,$(CC))) + $(warning Cannot build the IBS Linux driver with scan-build. Linux kernel files fail during the scan.) +else + $(MAKE) -C $(KDIR) M=$$PWD CFLAGS="$(CFLAGS) -mcmodel=kernel -mno-red-zone -fno-common" +endif + +clean: + $(MAKE) -C $(KDIR) M=$$PWD clean + rm -f $$PWD/*.ur-safe +endif + +``` + +`driver/ibs-core.c`: + +```c +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2015-2019 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * + * This file contains the core of the code for an IBS driver. User programs + * interface with this driver using device file system nodes at + * /dev/cpu//ibs/op and /dev/cpu//ibs/fetch, where + * represents an integer ID of a core in the system. For details about the user + * interface, see the code in this file, ibs-structs.h, and ibs-uapi.h. + */ +#include +#include +/* Older versions of Linux need device.h, so keep this */ +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +#include +#else +#include +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) +#include +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) || LINUX_VERSION_CODE >= KERNEL_VERSION(5,8,0) +#include +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) +#include +#endif + +#include "ibs-fops.h" +#include "ibs-interrupt.h" +#include "ibs-msr-index.h" +#include "ibs-structs.h" +#include "ibs-uapi.h" +#include "ibs-utils.h" +#include "ibs-workarounds.h" + +#define IBS_BUFFER_SIZE (PAGE_SIZE << 8) +#define IBS_OP_BUFFER_SIZE IBS_BUFFER_SIZE +#define IBS_FETCH_BUFFER_SIZE IBS_BUFFER_SIZE + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) && !defined(pr_warn) +#define pr_warn(fmt, ...) printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__) +#endif + +#if !defined(APIC_EILVTn) && !defined(APIC_EILVT0) +#define APIC_EILVT0 0x500 +#define APIC_EILVT_MSG_NMI 0x4 +#endif + +#if !defined (CPU_UP_CANCELED_FROZEN) +#define CPU_UP_CANCELED_FROZEN (CPU_UP_CANCELED | CPU_TASKS_FROZEN) +#endif + +void *pcpu_op_dev; +void *pcpu_fetch_dev; +static struct class *ibs_class; +static int ibs_major; + +static int ibs_fetch_supported = 0; +static int ibs_op_supported = 0; +static int ibs_brn_trgt_supported = 0; +static int ibs_op_cnt_ext_supported = 0; +static int ibs_rip_invalid_chk_supported = 0; +static int ibs_op_brn_fuse_supported = 0; +static int ibs_fetch_ctl_extd_supported = 0; +static int ibs_op_data4_supported = 0; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) +static int ibs_hotplug_notifier; +#endif + +/* Family 10h Erratum #420: Instruction-Based Sampling Engine May Generate + * Interrupt that Cannot Be Cleared */ +static int workaround_fam10h_err_420 = 0; +/* Family 15h Models 00h-1Fh Erratum 718: the processor only sets but never + * clears MSR C001_1037[3], [6], and [19]. */ +static int workaround_fam15h_err_718 = 0; +/* First-generation Family 17h processors do not necessarily enable IBS by + * default. They require setting some bits in each core to run IBS. + * This can be done with a BIOS setting on many boards, but we run the same + * settings in this driver to increase compatibility */ +static int workaround_fam17h_zn = 0; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0) +static struct notifier_block handle_ibs_nmi_notifier = +{ + .notifier_call = handle_ibs_nmi, + .next = NULL, + .priority = INT_MAX +}; +#endif + +static const struct file_operations ibs_fops = { + .open = ibs_open, + .owner = THIS_MODULE, + .poll = ibs_poll, + .read = ibs_read, + .release = ibs_release, + .unlocked_ioctl = ibs_ioctl, +}; + +static void init_ibs_dev(struct ibs_dev *dev, int cpu) +{ + mutex_init(&dev->read_lock); + init_waitqueue_head(&dev->readq); + init_waitqueue_head(&dev->pollq); + dev->cpu = cpu; + atomic_set(&dev->in_use, 0); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,11,0) + dev->bottom_half = IRQ_WORK_INIT_LAZY(&handle_ibs_work); +#else +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) + init_irq_work(&dev->bottom_half, &handle_ibs_work); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,5,0) + atomic_set(&dev->bottom_half.flags, IRQ_WORK_LAZY); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0) + dev->bottom_half.flags = IRQ_WORK_LAZY; +#endif // >= 5.5.0 or >= 3.9.0 +#endif // >= 2.6.37 +#endif // >= 5.11.0 + dev->ibs_fetch_supported = ibs_fetch_supported; + dev->ibs_op_supported = ibs_op_supported; + dev->ibs_brn_trgt_supported = ibs_brn_trgt_supported; + dev->ibs_op_cnt_ext_supported = ibs_op_cnt_ext_supported; + dev->ibs_rip_invalid_chk_supported = ibs_rip_invalid_chk_supported; + dev->ibs_op_brn_fuse_supported = ibs_op_brn_fuse_supported; + dev->ibs_fetch_ctl_extd_supported = ibs_fetch_ctl_extd_supported; + dev->ibs_op_data4_supported = ibs_op_data4_supported; + dev->workaround_fam10h_err_420 = workaround_fam10h_err_420; + dev->workaround_fam15h_err_718 = workaround_fam15h_err_718; + dev->workaround_fam17h_zn = workaround_fam17h_zn; +} + +static void init_ibs_op_dev(struct ibs_dev *dev, int cpu) +{ + init_ibs_dev(dev, cpu); + dev->flavor = IBS_OP; + dev->entry_size = sizeof(struct ibs_op); + mutex_init(&dev->ctl_lock); +} + +static void init_ibs_fetch_dev(struct ibs_dev *dev, int cpu) +{ + init_ibs_dev(dev, cpu); + dev->flavor = IBS_FETCH; + dev->entry_size = sizeof(struct ibs_fetch); + mutex_init(&dev->ctl_lock); +} + +static void ibs_setup_lvt(void *nothing) +{ + u64 ibs_control; + u8 offset; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) + unsigned long reg; + unsigned int v; +#endif + preempt_disable(); + rdmsrl(MSR_IBS_CONTROL, ibs_control); + if (!(ibs_control & IBS_LVT_OFFSET_VAL)) + goto fail; + offset = ibs_control & IBS_LVT_OFFSET; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) + if(!setup_APIC_eilvt(offset, 0, APIC_EILVT_MSG_NMI, 0)) + goto out; +#else +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30) + reg = (offset << 4) + APIC_EILVTn(0); +#else + reg = (offset << 4) + APIC_EILVT0; +#endif + v = (APIC_EILVT_MSG_NMI << 8); + apic_write(reg, v); + goto out; +#endif +fail: + pr_warn("IBS APIC setup fail on cpu %d\n", smp_processor_id()); +out: + preempt_enable(); + return; +} + +static int ibs_device_create(int flavor, int cpu) +{ + struct device *dev; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + dev = device_create(ibs_class, NULL, + MKDEV(ibs_major, IBS_MINOR(flavor, cpu)), NULL, + "ibs_%s_%u", + flavor == IBS_OP ? "op" : "fetch", cpu); +#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) + dev = device_create(ibs_class, NULL, + MKDEV(ibs_major, IBS_MINOR(flavor, cpu)), + "ibs_%u_%s", + cpu, flavor == IBS_OP ? "op" : "fetch"); +#else + dev = device_create(ibs_class, NULL, + MKDEV(ibs_major, IBS_MINOR(flavor, cpu)), NULL, + "cpu/%u/ibs/%s", + cpu, flavor == IBS_OP ? "op" : "fetch"); +#endif + return IS_ERR(dev) ? PTR_ERR(dev) : 0; +} + +static void ibs_device_destroy(int flavor, int cpu) +{ + device_destroy(ibs_class, MKDEV(ibs_major, IBS_MINOR(flavor, cpu))); +} + +/* When we're about to bring a CPU online, we need to create the fetch and op + devices for it. We create these when onlining and remove them when offlining + because this driver may be removed while the CPU is down, and we don't want + to lose any of the data structures or workaround state while we can't + talk to the CPU */ +static int ibs_prepare_up(unsigned int cpu) +{ + int err = 0; + pr_info("IBS: Creating IBS devices on core %u\n", cpu); + if (ibs_op_supported) + { + err = ibs_device_create(IBS_OP, cpu); + if (err) + return err; + } + if (ibs_fetch_supported) + { + err = ibs_device_create(IBS_FETCH, cpu); + if (err && ibs_op_supported) + ibs_device_destroy(IBS_OP, cpu); + } + return err; +} + +/* Once the CPU is actually online and we can talk to it/run code on it, + set up its LVT and do any core-specific workarounds that change CPU state */ +static int ibs_online_up(unsigned int cpu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) + ibs_prepare_up(cpu); +#endif + pr_info("IBS: Bringing up IBS on core %u\n", cpu); + ibs_setup_lvt(NULL); + if(workaround_fam17h_zn) + start_fam17h_zn_static_workaround(cpu); + return 0; +} + +/* CPU is actually offline at this point, so just destroy its data structs. */ +static int ibs_down(unsigned int cpu) +{ + pr_info("IBS: Core %u is down\n", cpu); + if (ibs_op_supported) + ibs_device_destroy(IBS_OP, cpu); + if (ibs_fetch_supported) + ibs_device_destroy(IBS_FETCH, cpu); + return 0; +} + +/* When we're about to take a CPU offline and we won't be able to run any code + on it. As such, we must remove any CPU state-changing workarounds that might + be forgotten if this driver is removed while the core is down. Also remove + any IBS interrupt-causing bits so that we don't get unhandled interrupts. */ +static int ibs_prepare_down(unsigned int cpu) +{ + pr_info("IBS: Starting to take down core %u\n", cpu); + disable_ibs_op_on_cpu(per_cpu_ptr(pcpu_op_dev, cpu), cpu); + disable_ibs_fetch_on_cpu(per_cpu_ptr(pcpu_fetch_dev, cpu), cpu); + if (workaround_fam17h_zn) + stop_fam17h_zn_static_workaround(cpu); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) + ibs_down(cpu); +#endif + return 0; +} + +/* After 4.10.0, we directly call the above helper functions from the + cpuhp_setup_state machine. No need to setup this generic callback */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) +static int ibs_class_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + unsigned int cpu = (unsigned long)hcpu; + int err = 0; + + switch (action) { + case CPU_UP_PREPARE: + err = ibs_prepare_up(cpu); + break; + case CPU_ONLINE: + ibs_online_up(cpu); + break; + case CPU_UP_CANCELED: + case CPU_UP_CANCELED_FROZEN: + case CPU_DEAD: + ibs_down(cpu); + break; + case CPU_DOWN_PREPARE: + ibs_prepare_down(cpu); + break; + } + return notifier_from_errno(err); +} + +#ifdef __refdata +static struct notifier_block __refdata ibs_class_cpu_notifier = { +#else +static struct notifier_block ibs_class_cpu_notifier = { +#endif + .notifier_call = ibs_class_cpu_callback, +}; +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +static char *ibs_devnode(struct device *dev, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,0) +umode_t *mode +#else +mode_t *mode +#endif +) +{ + int minor = MINOR(dev->devt); + + return kasprintf(GFP_KERNEL, "cpu/%u/ibs/%s", IBS_CPU(minor), + IBS_FLAVOR(minor) == IBS_OP ? "op" : "fetch"); +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +static int ibs_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + add_uevent_var(env, "DEVMODE=%#o", 0666); + return 0; +} +#endif + +static int check_for_ibs_support(void) +{ + unsigned int feature_id; + /* Must be on an AMD CPU */ + struct cpuinfo_x86 *c = &boot_cpu_data; + if (c->x86_vendor != X86_VENDOR_AMD) + { + pr_err("Unable to start IBS driver.\n"); + pr_err("This is not an AMD processor.\n"); + return -EINVAL; + } + + /* IBS support is included in Family 10h, 12h, 14h, 15h, 16h, and 17h + * 11h and everything before 10h did not have it. */ + if (c->x86 < 0x10 || c->x86 == 0x11) + { + pr_err("Processor too old to support IBS.\n"); + return -EINVAL; + } + + if (c->x86 == 0x10) + { + pr_info("IBS: Startup enabling workaround for " + "Family 10h Errata 420\n"); + workaround_fam10h_err_420 = 1; + } + + if (c->x86 == 0x15 && c->x86_model <= 0x1f) + { + pr_info("IBS: Startup enabling workaround for " + "Family 15h Models 00h-1Fh Errata 718\n"); + workaround_fam15h_err_718 = 1; + } + + feature_id = cpuid_ecx(0x80000001); + /* Check bit 10 of CPUID_Fn8000_0001_ECX for IBS support */ + if (!(feature_id & (1 << 10)) && !workaround_fam17h_zn) + { + // "Zen" core workaround. Multiple SoCs have "Zen" cores. + // 0x00-0x0f are the Ryzen/Epyc CPU-only SoCs + // 0x10-0x2f are the Raven Ridge APUs + // 0x50-0x5f also exist + if (c->x86 == 0x17 && + ((c->x86_model >= 0x0 && c->x86_model <= 0x2f) || + (c->x86_model >= 0x50 && c->x86_model < 0x5f))) + { + unsigned int cpu = 0; + pr_info("IBS: Startup enabling workaround for " + "Family 17h first-gen CPUs\n"); + workaround_fam17h_zn = 1; + for_each_online_cpu(cpu) { + start_fam17h_zn_static_workaround(cpu); + } + } + else + { + pr_err("CPUID_Fn8000_0001 indicates no IBS support.\n"); + return -EINVAL; + } + } + + if (workaround_fam17h_zn) + { + pr_info("IBS: This workaround may slow down your processor.\n"); + pr_info("IBS: Unload IBS driver to maximize performance.\n"); + } + + /* If we are here, time to check the IBS capability flags for + * what, if anything, is supported. */ + feature_id = cpuid_eax(0x8000001B); + + /* bit 0 is feature flags valid. If 0, die. */ + if (! (feature_id & 1)) + { + pr_err("CPUID_Fn8000_001B indicates no IBS support.\n"); + return -EINVAL; + } + /* Now check support for Op or Fetch sampling. If neither, die. */ + ibs_fetch_supported = feature_id & (1 << 1); + /* Op count is more complicated. We want all of its features in this + * driver, so or them all together */ + ibs_op_supported = feature_id & (1 << 2); + ibs_op_supported |= feature_id & (1 << 3); + ibs_op_supported |= feature_id & (1 << 4); + if (!ibs_fetch_supported && !ibs_op_supported) + { + pr_err("CPUID_Fn800_001B says no Op _or_ Fetch support.\n"); + return -EINVAL; + } + + /* Now to set all the other feature flags */ + ibs_brn_trgt_supported = feature_id & (1 << 5); + ibs_op_cnt_ext_supported = feature_id & (1 << 6); + ibs_rip_invalid_chk_supported = feature_id & (1 << 7); + ibs_op_brn_fuse_supported = feature_id & (1 << 8); + ibs_fetch_ctl_extd_supported = feature_id & (1 << 9); + ibs_op_data4_supported = feature_id & (1 << 10); + return 0; +} + +static void destroy_ibs_class(void) +{ + class_destroy(ibs_class); +} + +static void destroy_ibs_devices(void) +{ + unsigned int cpu = 0; + for_each_online_cpu(cpu) { + if (ibs_op_supported) + ibs_device_destroy(IBS_OP, cpu); + if (ibs_fetch_supported) + ibs_device_destroy(IBS_FETCH, cpu); + } +} + +static void destroy_ibs_hotplug(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) + get_online_cpus(); + cpuhp_remove_state(ibs_hotplug_notifier); + put_online_cpus(); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,15,0) + cpu_notifier_register_begin(); + __unregister_hotcpu_notifier(&ibs_class_cpu_notifier); + cpu_notifier_register_done(); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25) + get_online_cpus(); + unregister_hotcpu_notifier(&ibs_class_cpu_notifier); + put_online_cpus(); +#else + unregister_hotcpu_notifier(&ibs_class_cpu_notifier); +#endif +} + +static void unregister_ibs_chrdev(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + __unregister_chrdev(ibs_major, 0, NR_CPUS, "cpu/ibs"); +#else + unregister_chrdev(ibs_major, "cpu/ibs"); +#endif +} + +static void destroy_ibs_cpu_structs(void) +{ + unsigned int cpu = 0; + for_each_possible_cpu(cpu) { + free_ibs_buffer(per_cpu_ptr(pcpu_fetch_dev, cpu)); + free_ibs_buffer(per_cpu_ptr(pcpu_op_dev, cpu)); + if (workaround_fam17h_zn) + stop_fam17h_zn_static_workaround(cpu); + } + free_percpu(pcpu_fetch_dev); + free_percpu(pcpu_op_dev); + free_workaround_structs(); +} + +static __init int ibs_init(void) +{ + int err = 0; + unsigned int cpu = 0; + + err = check_for_ibs_support(); + if (err < 0) + goto out; + + pr_info("IBS: Initializing IBS module\n"); + + pcpu_op_dev = alloc_percpu(struct ibs_dev); + if (!pcpu_op_dev) + goto err_metadata; + pcpu_fetch_dev = alloc_percpu(struct ibs_dev); + if (!pcpu_fetch_dev) { + free_percpu(pcpu_op_dev); + goto err_metadata; + } + if (init_workaround_structs()) { + free_percpu(pcpu_op_dev); + free_percpu(pcpu_fetch_dev); +err_metadata: + pr_err("Failed to allocate IBS device metadata; exiting\n"); + err = -ENOMEM; + goto out; + } + + for_each_possible_cpu(cpu) { + init_ibs_op_dev(per_cpu_ptr(pcpu_op_dev, cpu), cpu); + err = setup_ibs_buffer(per_cpu_ptr(pcpu_op_dev, cpu), + IBS_OP_BUFFER_SIZE); + if (err) + goto err_buffers; + init_ibs_fetch_dev(per_cpu_ptr(pcpu_fetch_dev, cpu), cpu); + err = setup_ibs_buffer(per_cpu_ptr(pcpu_fetch_dev, cpu), + IBS_FETCH_BUFFER_SIZE); + if (err) { +err_buffers: + pr_err("CPU %d failed to allocate IBS device buffer; " + "exiting\n", cpu); + goto out_buffers; + } + init_workaround_initialize(); + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + ibs_major = __register_chrdev(0, 0, NR_CPUS, "cpu/ibs", &ibs_fops); +#else + ibs_major = register_chrdev(0, "cpu/ibs", &ibs_fops); +#endif + if (ibs_major < 0) { + pr_err("Failed to get IBS device number; exiting\n"); + goto out_buffers; + } + + ibs_class = class_create(THIS_MODULE, "ibs"); + if (IS_ERR(ibs_class)) { + err = PTR_ERR(ibs_class); + pr_err("Failed to create IBS class; exiting\n"); + goto out_chrdev; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + ibs_class->devnode = ibs_devnode; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + ibs_class->dev_uevent = ibs_uevent; +#endif + + /* Kernel versions older than 4.10 require some in-kernel locking + around registering the hotplug notifier, or they could deadlock. + See: https://patchwork.kernel.org/patch/3805711/ */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) + /* 4.10 says just to use get_online_cpus()s, see the Dec. 2016 version + https://www.kernel.org/doc/html/v4.11/core-api/cpu_hotplug.html */ + get_online_cpus(); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,15,0) + /* 3.15 introduced cpu_notifier_register_begin() for this. */ + cpu_notifier_register_begin(); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25) + get_online_cpus(); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) + on_each_cpu(ibs_setup_lvt, NULL, 1); +#else + on_each_cpu(ibs_setup_lvt, NULL, 1, 1); +#endif + +/* Set up the devices and the hotplug notifiers. */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) + /* Once we have cpuhp_setup_state(), this will also create the device + because ibs_online_up is called per device. */ + /* We currently set up everything on CPUHP_AP_ONLINE_DYN because it's + likely that we are not the first device on this chain. Current + kernels have a bug where, if you are the first device on a DYN + notifier chain, your removal from that chain will fail. See: + https://lkml.org/lkml/2017/7/5/574 */ + ibs_hotplug_notifier = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "cpu/ibs:online", ibs_online_up, ibs_prepare_down); + if (ibs_hotplug_notifier < 0) + goto out_chrdev; + put_online_cpus(); + +#else // < 4.10 + /* cpuhp_setup_state() is used in 4.10 and above. + This ends up calling the ibs_online_up notifier for each + CPU, which will actually perform the device creation the + first time through. So no need to do this. On older kernels, we + have to create the devices here. */ + for_each_online_cpu(cpu) { + if (ibs_op_supported) + err = ibs_device_create(IBS_OP, cpu); + if (err != 0) + goto out_class; + if (ibs_fetch_supported) + err = ibs_device_create(IBS_FETCH, cpu); + if (err != 0) + goto out_class; + } + /* After setting up the current CPUs' data structures, we register the + notifier for them. This changes depending on the kernel version */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,15,0) + __register_hotcpu_notifier(&ibs_class_cpu_notifier); + cpu_notifier_register_done(); +#else + register_hotcpu_notifier(&ibs_class_cpu_notifier); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25) + put_online_cpus(); +#endif // >= 2.6.25 +#endif // >= 3.15.0 +#endif // >= 4.10 + +/* Now set up the NMI handler */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0) + err = register_nmi_handler(NMI_LOCAL, handle_ibs_nmi, + NMI_FLAG_FIRST, "ibs_op"); +#else + err = register_die_notifier(&handle_ibs_nmi_notifier); +#endif + if (err) { + pr_err("Failed to register NMI handler; exiting\n"); + goto out_device; + } + + goto out; + +out_device: + destroy_ibs_devices(); +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) +out_class: +#endif + destroy_ibs_hotplug(); +out_chrdev: + unregister_ibs_chrdev(); +out_buffers: + destroy_ibs_cpu_structs(); +out: + return err; +} + +static __exit void ibs_exit(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0) + unregister_nmi_handler(NMI_LOCAL, "ibs_op"); +#else + unregister_die_notifier(&handle_ibs_nmi_notifier); +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,10,0) + /* We only need to do this on older kernels -- the CPU hotplug destroyer + will call into the per-device destroy functions on newer kernels */ + destroy_ibs_devices(); +#endif + destroy_ibs_hotplug(); + unregister_ibs_chrdev(); + destroy_ibs_cpu_structs(); + destroy_ibs_class(); + + pr_info("IBS: exited IBS module\n"); +} + +module_init(ibs_init); +module_exit(ibs_exit); + +MODULE_LICENSE("GPL v2"); + +``` + +`driver/ibs-fops.c`: + +```c +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2015-2018 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * This file contains the logic for user-level control of the driver through + * ioctl() commands sent to the device. + * The ioctl() options are described in the comments of ibs-uapi.h + */ +#include +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) +#include +#else +#include +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0) +#include +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +#include +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28) +#include +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +#include +#else +#include +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) +#undef atomic_long_xchg +#define atomic_long_xchg(v, new) (atomic_xchg((atomic64_t *)(v), (new))) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) && !defined(pr_warn) +#define pr_warn(fmt, ...) printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__) +#endif + +#include "ibs-fops.h" +#include "ibs-msr-index.h" +#include "ibs-structs.h" +#include "ibs-uapi.h" +#include "ibs-utils.h" +#include "ibs-workarounds.h" + +/* Declarations of all the devices on a per-cpu basis. + * Real declaration is in ibs-core.c */ +extern void *pcpu_op_dev; +extern void *pcpu_fetch_dev; + +static inline void enable_ibs_op_on_cpu(struct ibs_dev *dev, + const int cpu, const u64 op_ctl) +{ + if (dev->workaround_fam17h_zn) + start_fam17h_zn_dyn_workaround(cpu); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0) + wrmsrl_on_cpu(cpu, MSR_IBS_OP_CTL, op_ctl); +#else + wrmsr_on_cpu(cpu, MSR_IBS_OP_CTL, (u32)((u64)(op_ctl)), + (u32)((u64)(op_ctl) >> 32)); +#endif +} + +/* Disabling IBS op sampling takes a little bit of extra work. + * It is possible that an IBS op has been sampled, meaning that + * IbsOpVal is set. However, the NMI has not yet arrived from + * the APIC. As such, we will (in the very near future) enter + * the NMI handler. + * If we just fully zero out the IBS_OP_CTL register, then that + * NMI handler will see that IbsOpVal is zero and think that this + * NMI was not caused by an op sample. It would pass the interrupt + * on down the NMI chain, which could eventually lead to a system + * reboot (if, for instance, there is a watchdog timer enabled). + * We can't just read IbsOpVal, zero out IbsOpEn, and then save IbsOpVal + * back into the register; the NMI could arrive between the zeroing of + * the register and the resetting of IbsOpVal. There is no way to do an + * atomic read-modify-write to an MSR. + * Our solution is thus to force IbsOpVal to true, but zero out all the + * other bits. We wait for a microsecond (giving the APIC time to poke + * this core), then fully disable IBS_OP_CTL. This prevents the dangling + * IbsOpVal from inadvertently eating any real NMIs targetted at this + * core (except during this microsecond-long window, where we are spin + * looping and thus hopefully not producing any real work that would + * cause an NMI). */ +static void disable_ibs_op(void *info) +{ + wrmsrl(MSR_IBS_OP_CTL, IBS_OP_VAL); + udelay(1); + wrmsrl(MSR_IBS_OP_CTL, 0ULL); +} + +void disable_ibs_op_on_cpu(struct ibs_dev *dev, const int cpu) +{ + if (dev->workaround_fam10h_err_420) + do_fam10h_workaround_420(cpu); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) + smp_call_function_single(cpu, disable_ibs_op, NULL, 1); +#else + smp_call_function_single(cpu, disable_ibs_op, NULL, 1, 1); +#endif + if (dev->workaround_fam17h_zn) + stop_fam17h_zn_dyn_workaround(cpu); +} + +static inline void enable_ibs_fetch_on_cpu(struct ibs_dev *dev, + const int cpu, const u64 fetch_ctl) +{ + if (dev->workaround_fam17h_zn) + start_fam17h_zn_dyn_workaround(cpu); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0) + wrmsrl_on_cpu(cpu, MSR_IBS_FETCH_CTL, fetch_ctl); +#else + wrmsr_on_cpu(cpu, MSR_IBS_FETCH_CTL, (u32)((u64)(fetch_ctl)), + (u32)((u64)(fetch_ctl) >> 32)); +#endif +} + +void disable_ibs_fetch_on_cpu(struct ibs_dev *dev, const int cpu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0) + wrmsrl_on_cpu(cpu, MSR_IBS_FETCH_CTL, 0ULL); +#else + wrmsr_on_cpu(cpu, MSR_IBS_FETCH_CTL, 0UL, 0UL); +#endif + if (dev->workaround_fam17h_zn) + stop_fam17h_zn_dyn_workaround(cpu); +} + +static void set_ibs_defaults(struct ibs_dev *dev) +{ + atomic_long_set(&dev->poll_threshold, 1); + if (dev->flavor == IBS_OP) + { + if (dev->ibs_op_cnt_ext_supported) + { + dev->ctl = (scatter_bits(0, IBS_OP_CUR_CNT_23) | + scatter_bits(0x4000, IBS_OP_MAX_CNT) | + IBS_OP_CNT_CTL); + } + else + { + dev->ctl = (scatter_bits(0, IBS_OP_CUR_CNT_OLD) | + scatter_bits(0x4000, IBS_OP_MAX_CNT_OLD) | + IBS_OP_CNT_CTL); + } + } + else /* dev->flavor == IBS_FETCH */ + dev->ctl = (IBS_RAND_EN | + scatter_bits(0, IBS_FETCH_CNT) | + scatter_bits(0x1000, IBS_FETCH_MAX_CNT)); +} + +int ibs_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct ibs_dev *dev; + + if (IBS_FLAVOR(minor) == IBS_OP) + dev = per_cpu_ptr(pcpu_op_dev, IBS_CPU(minor)); + else /* IBS_FLAVOR(minor) == IBS_FETCH */ + dev = per_cpu_ptr(pcpu_fetch_dev, IBS_CPU(minor)); + + if (atomic_cmpxchg(&dev->in_use, 0, 1) != 0) + return -EBUSY; + + file->private_data = dev; + + mutex_lock(&dev->ctl_lock); + set_ibs_defaults(dev); + reset_ibs_buffer(dev); + mutex_unlock(&dev->ctl_lock); + + return 0; +} + +int ibs_release(struct inode *inode, struct file *file) +{ + struct ibs_dev *dev = file->private_data; + + mutex_lock(&dev->ctl_lock); + + if (dev->flavor == IBS_OP) + disable_ibs_op_on_cpu(dev, dev->cpu); + else /* dev->flavor == IBS_FETCH */ + disable_ibs_fetch_on_cpu(dev, dev->cpu); + + set_ibs_defaults(dev); + reset_ibs_buffer(dev); + + atomic_set(&dev->in_use, 0); + + mutex_unlock(&dev->ctl_lock); + + return 0; +} + +static ssize_t do_ibs_read(struct ibs_dev *dev, char __user *buf, size_t count) +{ + long rd = atomic_long_read(&dev->rd); + long wr = atomic_long_read(&dev->wr); + long entries = atomic_long_read(&dev->entries); + void *rd_ptr = dev->buf + rd * dev->entry_size; + long entries_read = 0; + + /* Read this much: */ + count = min(count, (size_t)(entries * dev->entry_size)); + if (count == 0) + return 0; + + if (rd < wr) { /* Buffer has not wrapped */ + if (copy_to_user(buf, rd_ptr, count)) + return -EFAULT; + } else { /* Buffer has wrapped */ + + /* First, read up to end of buffer */ + size_t bytes_to_end = (dev->capacity - rd) * dev->entry_size; + size_t bytes_to_read = min(count, bytes_to_end); + if (copy_to_user(buf, rd_ptr, bytes_to_read)) + return -EFAULT; + + /* If necessary, complete the read at buffer start */ + if (count > bytes_to_end) { + buf += bytes_to_end; + rd_ptr = dev->buf; + bytes_to_read = min(count - bytes_to_end, + (size_t)(wr * dev->entry_size)); + if (copy_to_user(buf, rd_ptr, bytes_to_read)) + return -EFAULT; + } + } + entries_read = count / dev->entry_size; + rd = (rd + entries_read) % dev->capacity; + atomic_long_set(&dev->rd, rd); + atomic_long_sub(entries_read, &dev->entries); + return count; +} + +ssize_t ibs_read(struct file *file, char __user *buf, size_t count, + loff_t *fpos) +{ + struct ibs_dev *dev = file->private_data; + ssize_t retval; + + if (count < dev->entry_size || count > dev->size) + return -EINVAL; + /* Make count a multiple of the entry size */ + count -= count % dev->entry_size; + + /* + * Assuming we are the sole reader, we will rarely spin on this lock. + */ + mutex_lock(&dev->read_lock); + while (!atomic_long_read(&dev->entries)) { /* No data */ + mutex_unlock(&dev->read_lock); + + /* If IBS is disabled, return nothing */ + mutex_lock(&dev->ctl_lock); + if ((dev->flavor == IBS_OP && !(dev->ctl & IBS_OP_EN)) || + (dev->flavor == IBS_FETCH && !(dev->ctl & IBS_FETCH_EN))) { + mutex_unlock(&dev->ctl_lock); + return 0; + } + mutex_unlock(&dev->ctl_lock); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + if (wait_event_interruptible(dev->readq, + (atomic_long_read(&dev->rd) != + atomic_long_read(&dev->wr)))) + return -ERESTARTSYS; + mutex_lock(&dev->read_lock); + } + retval = do_ibs_read(dev, buf, count); + mutex_unlock(&dev->read_lock); + return retval; +} + +unsigned int ibs_poll(struct file *file, poll_table *wait) +{ + struct ibs_dev *dev = file->private_data; + + /* Update the poll table in case nobody has data */ + poll_wait(file, &dev->pollq, wait); + + mutex_lock(&dev->read_lock); + if (atomic_long_read(&dev->entries) >= + atomic_long_read(&dev->poll_threshold)) { + mutex_unlock(&dev->read_lock); + return POLLIN | POLLRDNORM; /* There is enough data */ + } + mutex_unlock(&dev->read_lock); + + /* Check whether IBS is disabled */ + mutex_lock(&dev->ctl_lock); + if ((dev->flavor == IBS_OP && !(dev->ctl & IBS_OP_EN)) || + (dev->flavor == IBS_FETCH && !(dev->ctl & IBS_FETCH_EN))) { + mutex_unlock(&dev->ctl_lock); + return POLLHUP; + } + mutex_unlock(&dev->ctl_lock); + return 0; +} + +long ibs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + long retval = 0; + struct ibs_dev *dev = file->private_data; + int cpu = dev->cpu; + + /* Lock-free commands */ + switch (cmd) { + case DEBUG_BUFFER: + pr_info("cpu %d buffer: { wr = %lu; rd = %lu; entries = %lu; " + "lost = %lu; capacity = %llu; entry_size = %llu; " + "size = %llu; }\n", + cpu, + atomic_long_read(&dev->wr), + atomic_long_read(&dev->rd), + atomic_long_read(&dev->entries), + atomic_long_read(&dev->lost), + dev->capacity, + dev->entry_size, + dev->size); + return 0; + case GET_LOST: + return atomic_long_xchg(&dev->lost, 0); + case FIONREAD: + return atomic_long_read(&dev->entries); + } + + /* Commands that require the ctl_lock */ + mutex_lock(&dev->ctl_lock); + /* For SET* commands, ensure IBS is disabled */ + if (cmd == SET_CUR_CNT || cmd == SET_CNT || + cmd == SET_MAX_CNT || + cmd == SET_CNT_CTL || + cmd == SET_RAND_EN || + cmd == SET_POLL_SIZE || + cmd == SET_BUFFER_SIZE || + cmd == RESET_BUFFER) { + if ((dev->flavor == IBS_OP && dev->ctl & IBS_OP_EN) || + (dev->flavor == IBS_FETCH && dev->ctl & IBS_FETCH_EN)) { + mutex_unlock(&dev->ctl_lock); + return -EBUSY; + } + } + switch (cmd) { + case IBS_ENABLE: + if (dev->flavor == IBS_OP) { + dev->ctl |= IBS_OP_EN; + enable_ibs_op_on_cpu(dev, cpu, dev->ctl); + } else { /* dev->flavor == IBS_FETCH */ + dev->ctl |= IBS_FETCH_EN; + enable_ibs_fetch_on_cpu(dev, cpu, dev->ctl); + } + break; + case IBS_DISABLE: + if (dev->flavor == IBS_OP) { + disable_ibs_op_on_cpu(dev, cpu); + dev->ctl &= ~IBS_OP_EN; + } else { /* dev->flavor == IBS_FETCH */ + disable_ibs_fetch_on_cpu(dev, cpu); + dev->ctl &= ~IBS_FETCH_EN; + } + break; + case SET_CUR_CNT: + case SET_CNT: + if (dev->flavor == IBS_OP) { + if (dev->ibs_op_cnt_ext_supported) + { + dev->ctl &= ~IBS_OP_CUR_CNT_23; + dev->ctl |= scatter_bits(arg, IBS_OP_CUR_CNT_23); + } + else + { + dev->ctl &= ~IBS_OP_CUR_CNT_OLD; + dev->ctl |= scatter_bits(arg, IBS_OP_CUR_CNT_OLD); + } + } else { /* dev->flavor == IBS_FETCH */ + dev->ctl &= ~IBS_FETCH_CNT; + dev->ctl |= scatter_bits(arg, IBS_FETCH_CNT); + } + break; + case GET_CUR_CNT: + case GET_CNT: + if (dev->flavor == IBS_OP) + if (dev->ibs_op_cnt_ext_supported) + retval = gather_bits(dev->ctl, IBS_OP_CUR_CNT_23); + else + retval = gather_bits(dev->ctl, IBS_OP_CUR_CNT_OLD); + else /* dev->flavor == IBS_FETCH */ + retval = gather_bits(dev->ctl, IBS_FETCH_CNT); + break; + case SET_MAX_CNT: + if (dev->flavor == IBS_OP) { + if (dev->ibs_op_cnt_ext_supported) + { + dev->ctl &= ~IBS_OP_MAX_CNT; + dev->ctl |= scatter_bits(arg, IBS_OP_MAX_CNT); + } + else + { + dev->ctl &= ~IBS_OP_MAX_CNT_OLD; + dev->ctl |= scatter_bits(arg, IBS_OP_MAX_CNT_OLD); + } + } else { /* dev->flavor == IBS_FETCH */ + dev->ctl &= ~IBS_FETCH_MAX_CNT; + dev->ctl |= scatter_bits(arg, IBS_FETCH_MAX_CNT); + } + break; + case GET_MAX_CNT: + if (dev->flavor == IBS_OP) + if (dev->ibs_op_cnt_ext_supported) + retval = gather_bits(dev->ctl, IBS_OP_MAX_CNT); + else + retval = gather_bits(dev->ctl, IBS_OP_MAX_CNT_OLD); + else /* dev->flavor == IBS_FETCH */ + retval = gather_bits(dev->ctl, IBS_FETCH_MAX_CNT); + break; + case SET_CNT_CTL: + if (dev->flavor != IBS_OP) + retval = -EINVAL; + else if (arg == 1) + dev->ctl |= IBS_OP_CNT_CTL; + else if (arg == 0) + dev->ctl &= ~IBS_OP_CNT_CTL; + else + retval = -EINVAL; + break; + case GET_CNT_CTL: + if (dev->flavor != IBS_OP) + retval = -EINVAL; + else + retval = (dev->ctl & IBS_OP_CNT_CTL) ? 1 : 0; + break; + case SET_RAND_EN: + if (dev->flavor != IBS_FETCH) + retval = -EINVAL; + else if (arg == 1) + dev->ctl |= IBS_RAND_EN; + else if (arg == 0) + dev->ctl &= ~IBS_RAND_EN; + else + retval = -EINVAL; + break; + case GET_RAND_EN: + if (dev->flavor != IBS_FETCH) + retval = -EINVAL; + else + retval = (dev->ctl & IBS_RAND_EN) ? 1 : 0; + break; + case SET_POLL_SIZE: + if (0 < arg && arg < dev->capacity) + atomic_long_set(&dev->poll_threshold, arg); + else + retval = -EINVAL; + break; + case GET_POLL_SIZE: + retval = atomic_long_read(&dev->poll_threshold); + break; + case SET_BUFFER_SIZE: + /* Ensure requested buffer can hold at least one entry */ + if (arg < dev->entry_size) { + retval = -EINVAL; + break; + } + /* Do not re-allocate if there is no change */ + if (arg == dev->size) { + reset_ibs_buffer(dev); + break; + } + + free_ibs_buffer(dev); + retval = setup_ibs_buffer(dev, arg); + if (retval) + pr_warn("Failed to set IBS %s cpu %d buffer size to %ld; " + "leaving buffer unchanged\n", + dev->flavor == IBS_OP ? "op" : "fetch", + dev->cpu, arg); + break; + case GET_BUFFER_SIZE: + retval = dev->size; + break; + case RESET_BUFFER: + reset_ibs_buffer(dev); + break; + default: /* Command not recognized */ + retval = -ENOTTY; + break; + } + mutex_unlock(&dev->ctl_lock); + return retval; +} + +``` + +`driver/ibs-fops.h`: + +```h +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2017 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * This file contains the logic for user-level control of the driver through + * file-system commands such as ioctl() and read(). + * The ioctl() options are described in the comments of ibs-uapi.h + */ +#ifndef IBS_FOPS_H +#define IBS_FOPS_H + +#include + +#include "ibs-structs.h" +/** + * ibs_open - open a device node instance + */ +int ibs_open(struct inode *inode, struct file *file); + +/** + * ibs_poll - check device for read readiness + * + * Returns: (POLLIN | POLLRDNORM) if enough data is available for immediate + * read ("enough" is determined by poll_threshold--see struct ibs_dev for + * details); 0 if read would block to wait for data; or POLLHUP if there is not + * enough data and IBS is disabled. + */ +unsigned int ibs_poll(struct file *file, poll_table *wait); + +/** + * ibs_read - read IBS samples from the device + * @count: number of bytes to read; value must be (1) at least the size of + * one entry and (2) no more than the total buffer size + * + * This function reads as many IBS observations as possible up to @count bytes. + * + * Returns: Number of bytes read, or negative error code + */ +ssize_t ibs_read(struct file *file, char __user *buf, size_t count, + loff_t *fpos); + +/** + * ibs_release - disable IBS and clear the data buffer + */ +int ibs_release(struct inode *inode, struct file *file); + +/** + * ibs_ioctl() - perform an ioctl command + * + * The command that is called with a user sends an ioctl() to an IBS device. + * + * The details of the commands and what they do are contained in ibs-uapi.h + */ +long ibs_ioctl(struct file *file, unsigned int cmd, unsigned long arg); + +/** + * disable_ibs_op_on_cpu() + * + * This forcibly disables IBS Op sampling on a target CPU. + */ +void disable_ibs_op_on_cpu(struct ibs_dev *dev, const int cpu); + +/** + * disable_ibs_fetch_on_cpu() + * + * This forcibly disables IBS Fetch sampling on a target CPU. + */ +void disable_ibs_fetch_on_cpu(struct ibs_dev *dev, const int cpu); +#endif /* IBS_FOPS_H */ + +``` + +`driver/ibs-interrupt.c`: + +```c +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2015-2018 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * This is the NMI/interrupt handling code that is called whenever IBS samples + * are taken in the hardware. + */ +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +#include +#else +#include +#endif +#include + +#include "ibs-msr-index.h" +#include "ibs-interrupt.h" +#include "ibs-structs.h" + +extern void *pcpu_op_dev; +extern void *pcpu_fetch_dev; + +static inline void wake_up_queues(struct ibs_dev *dev) +{ + wake_up(&dev->readq); + if (atomic_long_read(&dev->entries) >= + atomic_long_read(&dev->poll_threshold)) + { + wake_up(&dev->pollq); + } +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) +void handle_ibs_work(struct irq_work *w) +{ + struct ibs_dev *dev = container_of(w, struct ibs_dev, bottom_half); + wake_up_queues(dev); +} +#endif + +/** + * lfsr_random - 16-bit Linear Feedback Shift Register (LFSR) + * + * LFSR from Paul Drongowski + */ +static inline unsigned int lfsr_random(void) +{ + static unsigned int lfsr_value = 0xF00D; + unsigned int bit; + + /* Compute next bit to shift in */ + bit = ((lfsr_value >> 0) ^ + (lfsr_value >> 2) ^ + (lfsr_value >> 3) ^ + (lfsr_value >> 5)) & 0x0001; + + /* Advance to next register value */ + lfsr_value = (lfsr_value >> 1) | (bit << 15); + + return lfsr_value; +} + +/* + * randomize_op_ctl - perform the pre-reenable randomization of op_ctl bits + */ +static inline u64 randomize_op_ctl(u64 op_ctl) +{ + u64 random_bits = lfsr_random() & 0xf; /* Get 4 random bits */ + return ((random_bits << 32) | (op_ctl & ~(0xfULL << 32))); +} + +static inline void enable_ibs_op(const u64 op_ctl) +{ + /* No need for Fam 17h workaround here: + * This function is only called when IBS is "enabled" in the driver + * but is paused during the NMI handling of the IBS interrupt. */ + wrmsrl(MSR_IBS_OP_CTL, op_ctl); +} + +static inline void enable_ibs_fetch(const u64 fetch_ctl) +{ + /* No need for the major Fam. 17h workaround here: + * This function is only called when IBS is "enabled" in the driver + * but is paused during the NMI handling of the IBS interrupt. */ + + /* The definition of IbsFetchVal in Families 15h and 17h is somewhat + * opaque. It is described as read-only but it must be reset in order + * to allow the fetch counter to start counting. On 17h, especially, + * this means that we must actually zero out the whole register before + * we can turn on fetch sampling. We do this on all cores to simplify + * the control logic. */ + u64 zero = 0LL; + wrmsrl(MSR_IBS_FETCH_CTL, zero); + wrmsrl(MSR_IBS_FETCH_CTL, fetch_ctl); +} + +/** + * collect_op_data - fill fields of ibs_op specific to op flavor + */ +static inline void collect_op_data(struct ibs_dev *dev, struct ibs_op *sample) +{ + rdmsrl(MSR_IBS_OP_RIP, sample->op_rip); + rdmsrl(MSR_IBS_OP_DATA, sample->op_data); + rdmsrl(MSR_IBS_OP_DATA2, sample->op_data2); + rdmsrl(MSR_IBS_OP_DATA3, sample->op_data3); + if (dev->ibs_op_data4_supported) + rdmsrl(MSR_IBS_OP_DATA4, sample->op_data4); + else + sample->op_data4 = 0ULL; + rdmsrl(MSR_IBS_DC_LIN_AD, sample->dc_lin_ad); + rdmsrl(MSR_IBS_DC_PHYS_AD, sample->dc_phys_ad); + if (dev->ibs_brn_trgt_supported) + rdmsrl(MSR_IBS_BR_TARGET, sample->br_target); + else + sample->br_target = 0ULL; +} + +/** + * collect_fetch_data - fill fields of ibs_fetch specific to fetch flavor + */ +static inline void collect_fetch_data(struct ibs_dev *dev, struct ibs_fetch *sample) +{ + rdmsrl(MSR_IBS_FETCH_CTL, sample->fetch_ctl); + if (dev->ibs_fetch_ctl_extd_supported) + rdmsrl(MSR_IBS_EXTD_CTL, sample->fetch_ctl_extd); + else + sample->fetch_ctl_extd = 0ULL; + rdmsrl(MSR_IBS_FETCH_LIN_AD, sample->fetch_lin_ad); + rdmsrl(MSR_IBS_FETCH_PHYS_AD, sample->fetch_phys_ad); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0) +#define AMD_IBS_RDTSC(x) (x) = rdtsc_ordered() +#else +#define AMD_IBS_RDTSC(x) rdtscll(x) +#endif + +/** + * collect_common_data - fill fields common to both fetch and op flavors + * @sample: ptr to either struct ibs_op or struct ibs_fetch + */ +#define collect_common_data(sample) \ + do { \ + AMD_IBS_RDTSC(sample->tsc); \ + asm ("movq %%cr3, %%rax\n\t" \ + "movq %%rax, %0" \ + : "=m"(sample->cr3) \ + : /* no input */ \ + : "%rax" \ + ); \ + sample->tid = current->pid; \ + sample->pid = current->tgid; \ + sample->cpu = smp_processor_id(); \ + sample->kern_mode = !user_mode(regs); \ + } while (0) + +static inline void handle_ibs_op_event(struct pt_regs *regs) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33) + struct ibs_dev *dev = this_cpu_ptr(pcpu_op_dev); +#else + struct ibs_dev *dev = per_cpu_ptr(pcpu_op_dev, smp_processor_id()); +#endif + unsigned int old_wr = atomic_long_read(&dev->wr); + unsigned int new_wr = (old_wr + 1) % dev->capacity; + struct ibs_op *sample; + u64 tmp; + + /* See do_fam10h_workaround_420() definition for details */ + rdmsrl(MSR_IBS_OP_CTL, tmp); + if (dev->workaround_fam10h_err_420 && !(tmp & IBS_OP_MAX_CNT_OLD)) + return; + + /* See disable_ibs_op() definition for more detals about why we + * potentially want to skip this IBS op sample. */ + if (!(tmp & IBS_OP_MAX_CNT)) + return; + + if (new_wr == atomic_long_read(&dev->rd)) { /* Full buffer */ + atomic_long_inc(&dev->lost); + goto out; + } + sample = (struct ibs_op *)(dev->buf + (old_wr * dev->entry_size)); + + collect_op_data(dev, sample); + + /* Logically this is part of collect_common_data. However we can save + * an MSR access beacause we already read the MSR_IBS_OP_CTL */ + sample->op_ctl = tmp; + collect_common_data(sample); + + atomic_long_set(&dev->wr, new_wr); + atomic_long_inc(&dev->entries); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) + irq_work_queue(&dev->bottom_half); +#else + /* Add more work directly into the NMI handler, but in older kernels, we + * didn't have access to IRQ work queues. */ + wake_up_queues(dev); +#endif + +out: + tmp = randomize_op_ctl(dev->ctl); + if (dev->workaround_fam15h_err_718) + wrmsrl(MSR_IBS_OP_DATA3, 0ULL); + enable_ibs_op(tmp); +} + +static inline void handle_ibs_fetch_event(struct pt_regs *regs) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33) + struct ibs_dev *dev = this_cpu_ptr(pcpu_fetch_dev); +#else + struct ibs_dev *dev = per_cpu_ptr(pcpu_fetch_dev, smp_processor_id()); +#endif + unsigned int old_wr = atomic_long_read(&dev->wr); + unsigned int new_wr = (old_wr + 1) % dev->capacity; + struct ibs_fetch *sample; + + if (new_wr == atomic_long_read(&dev->rd)) { /* Full buffer */ + atomic_long_inc(&dev->lost); + goto out; + } + sample = (struct ibs_fetch *)(dev->buf + (old_wr * dev->entry_size)); + + collect_fetch_data(dev, sample); + collect_common_data(sample); + + atomic_long_set(&dev->wr, new_wr); + atomic_long_inc(&dev->entries); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) + irq_work_queue(&dev->bottom_half); +#else + /* Add more work directly into the NMI handler, but in older kernels, we + * didn't have access to IRQ work queues. */ + wake_up_queues(dev); +#endif + +out: + enable_ibs_fetch(dev->ctl); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0) +static inline int handle_ibs_event(struct pt_regs *regs) +{ + u64 tmp; + int retval = NMI_DONE; + + /* Check for op sample */ + rdmsrl(MSR_IBS_OP_CTL, tmp); + if (tmp & IBS_OP_VAL) { + handle_ibs_op_event(regs); + retval += NMI_HANDLED; + } + + /* Check for fetch sample */ + rdmsrl(MSR_IBS_FETCH_CTL, tmp); + if (tmp & IBS_FETCH_VAL) { + handle_ibs_fetch_event(regs); + retval += NMI_HANDLED; + } + + /* Return immediately if both checks fail */ + return retval; +} + +int handle_ibs_nmi(unsigned int cmd, struct pt_regs *regs) +{ + return handle_ibs_event(regs); +} +#else /* Kernel version is below 3.2.0 */ + +#define NMI_DONE NOTIFY_OK +#define NMI_HANDLED NOTIFY_STOP + +static inline int handle_ibs_event(struct pt_regs *regs) +{ + u64 tmp; + int retval = 0; + + /* Check for op sample */ + rdmsrl(MSR_IBS_OP_CTL, tmp); + if (tmp & IBS_OP_VAL) { + handle_ibs_op_event(regs); + retval++; + } + + /* Check for fetch sample only if no op samples were avilable. + * We choose to have only one NMI succeed on these older kernels, + * becuse otherwise the queued up NMI work spits out angry messages + * to dmesg about unhandled NMIs. */ + rdmsrl(MSR_IBS_FETCH_CTL, tmp); + if (retval == 0 && (tmp & IBS_FETCH_VAL)) { + handle_ibs_fetch_event(regs); + retval++; + } + + /* If either check succeeds, let's assume we were the source of the NMI */ + return retval; +} + +int handle_ibs_nmi(struct notifier_block *self, unsigned long cmd, + void *data) +{ + struct die_args *args = (struct die_args *)data; + + if (cmd != DIE_NMI) + return NOTIFY_OK; + + /* Ignore memory / I/O errors */ + if (args->err & 0xc0) + return NOTIFY_OK; + + if (handle_ibs_event(args->regs) > 0) + return NOTIFY_STOP; + else + return NOTIFY_OK; +} +#endif + +``` + +`driver/ibs-interrupt.h`: + +```h +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * This is the NMI/interrupt handling code that is called whenever IBS samples + * are taken in the hardware. + */ +#ifndef IBS_INTERRUPT_H +#define IBS_INTERRUPT_H + +#include +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) +#include +/* If possible, we want to wake up clients that are poll()ing on reads in + * a bottom-half work queue. That's what this does. */ +void handle_ibs_work(struct irq_work *w); +#endif + +/* This is the actual interrupt handler, safe for running in NMI context, + * that reads the IBS values and dumps them into memory. */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0) +int handle_ibs_nmi(unsigned int cmd, struct pt_regs *regs); +#else /* Kernel version is below 3.2.0 */ +int handle_ibs_nmi(struct notifier_block *self, unsigned long cmd, + void *data); +#endif + +#endif /* IBS_INTERRUPT_H */ + +``` + +`driver/ibs-structs.h`: + +```h +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2015-2018 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * + * This file contains the structs used to communicate IBS samples from the + * driver into user-space. + */ +#ifndef IBS_STRUCTS_H +#define IBS_STRUCTS_H + +#include +#include +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) +#include +#endif + +struct ibs_op { + __u64 op_ctl; + __u64 op_rip; + __u64 op_data; + __u64 op_data2; + __u64 op_data3; + __u64 op_data4; + __u64 dc_lin_ad; + __u64 dc_phys_ad; + __u64 br_target; + __u64 tsc; + __u64 cr3; + pid_t tid; + pid_t pid; + int cpu; + int kern_mode; +}; + +struct ibs_fetch { + __u64 fetch_ctl; + __u64 fetch_ctl_extd; + __u64 fetch_lin_ad; + __u64 fetch_phys_ad; + __u64 tsc; + __u64 cr3; + pid_t tid; + pid_t pid; + int cpu; + int kern_mode; +}; + +struct ibs_dev { + char *buf; /* buffer memory region */ + u64 size; /* size of buffer memory region in bytes */ + u64 entry_size; /* size of each entry in bytes */ + u64 capacity; /* buffer capacity in entries */ + + atomic_long_t wr; /* write index (0 <= wr < capacity) */ + atomic_long_t rd; /* read index */ + atomic_long_t entries; /* buffer occupancy in entries */ + atomic_long_t lost; /* dropped samples counter */ + struct mutex read_lock; /* read lock */ + + wait_queue_head_t readq; /* wait queue for blocking read */ + wait_queue_head_t pollq; /* dedicated wait queue for polling */ + atomic_long_t poll_threshold; /* min size for poll to return */ + + u64 ctl; /* copy of op/fetch ctl MSR to store control options */ + struct mutex ctl_lock; /* lock for device control options */ + + int cpu; /* this device's cpu id */ + int flavor; /* IBS_FETCH or IBS_OP */ + atomic_t in_use; /* nonzero when device is open */ + + /* Information about what IBS stuff is supported on this CPU */ + int ibs_fetch_supported; + int ibs_op_supported; + int ibs_brn_trgt_supported; + int ibs_op_cnt_ext_supported; + int ibs_rip_invalid_chk_supported; + int ibs_op_brn_fuse_supported; + int ibs_fetch_ctl_extd_supported; + int ibs_op_data4_supported; + int workaround_fam10h_err_420; + int workaround_fam15h_err_718; + int workaround_fam17h_zn; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) + struct irq_work bottom_half; +#endif +}; + +#endif /* IBS_STRUCTS_H */ + +``` + +`driver/ibs-utils.c`: + +```c +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * These functions are useful across various files in the IBS driver, so they + * are included in this general utilities file. + */ +#include +#include +#include + +#include "ibs-utils.h" + +int reset_ibs_buffer(struct ibs_dev *dev) +{ + if (dev == NULL) + return -EACCES; + atomic_long_set(&dev->wr, 0); + atomic_long_set(&dev->rd, 0); + atomic_long_set(&dev->entries, 0); + atomic_long_set(&dev->lost, 0); + return 0; +} + +int setup_ibs_buffer(struct ibs_dev *dev, u64 size) +{ + void *tmp; + if (dev == NULL || size == 0) + return -EACCES; + + tmp = vmalloc(size); + if (!tmp) + return -ENOMEM; + + dev->buf = tmp; + dev->size = size; + dev->capacity = size / dev->entry_size; + + reset_ibs_buffer(dev); + + return 0; +} +int free_ibs_buffer(struct ibs_dev *dev) +{ + if (dev == NULL) + return -EACCES; + vfree(dev->buf); + return 0; +} + +u64 scatter_bits(u64 qty, u64 fmt) +{ + u64 reg = 0; + u64 qty_pos = 1; + u64 fmt_pos = 1; + u8 i; + for (i = 0; i < 64; i++) { + if (fmt & fmt_pos) { + if (qty & qty_pos) { + reg |= fmt_pos; + } + qty_pos <<= 1; + } + fmt_pos <<= 1; + } + return reg; +} + +u64 gather_bits(u64 reg, u64 fmt) +{ + u64 qty = 0; + u64 qty_pos = 1; + u64 fmt_pos = 1; + u8 i; + for (i = 0; i < 64; i++) { + if (fmt & fmt_pos) { + if (reg & fmt_pos) { + qty |= qty_pos; + } + qty_pos <<= 1; + } + fmt_pos <<= 1; + } + return qty; +} + +``` + +`driver/ibs-utils.h`: + +```h +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * These functions are useful across various files in the IBS driver, so they + * are included in this general utilities file. + */ +#ifndef IBS_UTILS_H +#define IBS_UTILS_H + +#include + +#include "ibs-structs.h" + + /* IBS flavors as ints */ +#define IBS_OP 0 +#define IBS_FETCH 1 + +/* Convert (flavor, cpu) to minor device number and back */ +#define IBS_MINOR(flavor, cpu) ((cpu << 1) | flavor) +#define IBS_CPU(minor) (minor >> 1) +#define IBS_FLAVOR(minor) (minor & 1) + +/* Remove all entries in the current IBS sample buffer for the target device */ +int reset_ibs_buffer(struct ibs_dev *dev); + +/* Create the buffer that will store IBS samples for this target device */ +int setup_ibs_buffer(struct ibs_dev *dev, u64 size); + +/* Free any allocations done after you're finished with a sample buffer in + * the target device. */ +int free_ibs_buffer(struct ibs_dev *dev); + +/** + * scatter_bits() - "scatter" a quantity over a certain positions + * @qty: data to scatter, packed densely into the low bits + * @fmt: scatter pattern + * + * Return: bitmask with quantity represented in specified bits + */ +u64 scatter_bits(u64 qty, u64 fmt); + +/** + * gather_bits() - "gather" bits from certain positions + * @reg: data "register" + * @fmt: mask of bit positions from which to gather + * + * Return: quantity represented by the specified bits + */ +u64 gather_bits(u64 reg, u64 fmt); + +#endif /* IBS_UTILS_H */ + +``` + +`driver/ibs-workarounds.c`: + +```c +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2015-2018 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * This file contains a series of workarounds for various generations of AMD + * CPUs. This prevents the main driver code from being polluted by details of + * the main IBS driver. + */ +/* This needs to come first because some old kernels need the u32/u64 defs. + * However, places like msr.h also use them. So put this at the top. */ +#include + +#include +#include +#include +#include +#include + +#include "ibs-structs.h" +#include "ibs-uapi.h" +#include "ibs-msr-index.h" + +#ifndef topology_sibling_cpumask +#define topology_sibling_cpumask(cpu) (per_cpu(cpu_sibling_map, cpu)) +#endif + +#define FAM17H_MSR_WA_1 0xc0011020 +#define FAM17H_MSR_WA_1_BITS 0x40000000000000ULL +#define FAM17H_MSR_WA_2 0xc0011029 +#define FAM17H_MSR_WA_2_BITS 0x80000ULL +#define FAM17H_MSR_WA_3 0xc0010296 +#define FAM17H_MSR_WA_3_BITS 0x404040ULL +#define CPUID_EXT_FEATURES 0xc0011005 +/* Storage for old MSR values that are changed when enabling IBS + * on Family 17h processors with "Zen" CPUs. We assume this IBS driver + * is the only thing to change them, and that they are the same per core. + * As such, we only have one value for the whole system so that + * we can know what to set them back to. */ +static u64 fam17h_old_1 = 0; +static u64 fam17h_old_2 = 0; +static u64 fam17h_old_3 = 0; +/* Need to keep track of whether Op, Fetch, or both are on + * so that when we are doing the MSR workaround, we only turn it off when + * both devices are disabled */ +static int* pcpu_num_devices_enabled; +static spinlock_t * pcpu_workaround_lock; +static int workarounds_started = 0; + +/* Different kernels had different ways of performing a 64-bit rdmsr and + * wrmsr commands on target CPUs. + * To prevent us from having ifdefs all over the place, these functions + * will do the same thing on various kernels. */ +static inline u64 custom_rdmsrl_on_cpu(unsigned int cpu, u32 msr_no) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0) + u64 ret_val; + rdmsrl_on_cpu(cpu, msr_no, &ret_val); + return ret_val; +#else + u32 lo, hi; + rdmsr_on_cpu(cpu, msr_no, &lo, &hi); + return (u64)lo | ((u64)hi << 32ULL); +#endif +} + +static inline void custom_wrmsrl_on_cpu(unsigned int cpu, u32 msr_no, u64 val) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0) + wrmsrl_on_cpu(cpu, msr_no, val); +#else + u32 lo, hi; + lo = val & 0xffffffff; + hi = val >> 32; + wrmsr_on_cpu(cpu, msr_no, lo, hi); +#endif +} + +/* When performing the workarounds for Family 17h first-generation CPUs, we + * want to store off the default values of a series of registers so we can + * restore the bits we will change after we are done. */ +static void init_fam17h_zn_workaround(void) +{ + rdmsrl(FAM17H_MSR_WA_1, fam17h_old_1); + rdmsrl(FAM17H_MSR_WA_2, fam17h_old_2); + rdmsrl(FAM17H_MSR_WA_3, fam17h_old_3); +} + +int init_workaround_structs(void) +{ + struct cpuinfo_x86 *c = &boot_cpu_data; + if (workarounds_started) + return 0; + + if (c->x86_vendor == X86_VENDOR_AMD && c->x86 == 0x17 && + c->x86_model == 0x1) + { + init_fam17h_zn_workaround(); + } + pcpu_num_devices_enabled = alloc_percpu(int); + if (!pcpu_num_devices_enabled) + return -1; + pcpu_workaround_lock = alloc_percpu(spinlock_t); + if (!pcpu_workaround_lock) { + free_percpu(pcpu_num_devices_enabled); + return -1; + } + workarounds_started = 1; + return 0; +} + +void free_workaround_structs(void) +{ + if (workarounds_started) + { + free_percpu(pcpu_num_devices_enabled); + free_percpu(pcpu_workaround_lock); + } +} + +void init_workaround_initialize(void) +{ + unsigned int cpu = 0; + if (!workarounds_started) + return; + for_each_possible_cpu(cpu) { + spinlock_t *workaround_lock; + int *num_devs = per_cpu_ptr(pcpu_num_devices_enabled, cpu); + *num_devs = 0; + workaround_lock = per_cpu_ptr(pcpu_workaround_lock, cpu); + spin_lock_init(workaround_lock); + } +} + +/* Enabling IBS on Family 17h processors with first-generation CPUs requires + * unsetting some bits in various MSRs so long as any IBS samples can flow + * through the pipeline. This function reads those MSRs out, sets a global view + * of the default state of those bits, and unsets them on the local core. + * This must be called before writing the enable bit into IBS_OP_CTL or + * IBS_FETCH_CTL. */ +static void enable_fam17h_zn_dyn_workaround(const int cpu) +{ + __u64 set_bits; + __u64 op_ctl, fetch_ctl; + __u64 cur1, cur3; + unsigned int cpu_to_use = 0; + /* Check if both op and fetch are off */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + /* for_each_cpu() was in 2.6.28, but not topology_sibilng_cpumask() */ + for_each_cpu(cpu_to_use, topology_sibling_cpumask(cpu)) +#else + for_each_cpu_mask(cpu_to_use, topology_core_siblings(cpu)) +#endif + { + op_ctl = custom_rdmsrl_on_cpu(cpu_to_use, MSR_IBS_OP_CTL); + fetch_ctl = custom_rdmsrl_on_cpu(cpu_to_use, MSR_IBS_FETCH_CTL); + /* Workaround already enabled if any other IBS is enabled + * on this physical core. Skip the workaround. */ + if ((op_ctl & IBS_OP_EN) || (fetch_ctl & IBS_FETCH_EN)) + return; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + cpu_to_use = cpumask_first(topology_sibling_cpumask(cpu)); +#else + cpu_to_use = first_cpu(topology_core_siblings(cpu)); +#endif + cur1 = custom_rdmsrl_on_cpu(cpu_to_use, FAM17H_MSR_WA_1); + cur3 = custom_rdmsrl_on_cpu(cpu_to_use, FAM17H_MSR_WA_3); + /* Set some bits on workaround MSR 1 then write back. */ + set_bits = cur1 | FAM17H_MSR_WA_1_BITS; + custom_wrmsrl_on_cpu(cpu_to_use, FAM17H_MSR_WA_1, set_bits); + /* Unset some bits on workaround MSR 3 then write back */ + set_bits = cur3 & ~FAM17H_MSR_WA_3_BITS; + custom_wrmsrl_on_cpu(cpu_to_use, FAM17H_MSR_WA_3, set_bits); +} + +static void disable_fam17h_zn_dyn_workaround(const int cpu) +{ + __u64 op_ctl, fetch_ctl; + __u64 cur1, cur3, set_bits; + unsigned int cpu_to_use = 0; + /* Check if both op and fetch are off */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + for_each_cpu(cpu_to_use, topology_sibling_cpumask(cpu)) +#else + for_each_cpu_mask(cpu_to_use, topology_core_siblings(cpu)) +#endif + { + op_ctl = custom_rdmsrl_on_cpu(cpu_to_use, MSR_IBS_OP_CTL); + fetch_ctl = custom_rdmsrl_on_cpu(cpu_to_use, MSR_IBS_FETCH_CTL); + /* Can't turn off the workaround while any IBS stuff is on */ + if ((op_ctl & IBS_OP_EN) || (fetch_ctl & IBS_FETCH_EN)) + return; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + cpu_to_use = cpumask_first(topology_sibling_cpumask(cpu)); +#else + cpu_to_use = first_cpu(topology_core_siblings(cpu)); +#endif + /* First time we're enabling IBS on this physical core; set the bits */ + /* Read current values */ + cur1 = custom_rdmsrl_on_cpu(cpu_to_use, FAM17H_MSR_WA_1); + cur3 = custom_rdmsrl_on_cpu(cpu_to_use, FAM17H_MSR_WA_3); + /* Unset some bits on workaround MSR 1 (if they were originally + * unset) and then write the new register back */ + set_bits = cur1 & ~FAM17H_MSR_WA_1_BITS; + set_bits |= fam17h_old_1; + custom_wrmsrl_on_cpu(cpu_to_use, FAM17H_MSR_WA_1, set_bits); + /* Set some bits on workaround MSR 3 (if they were originally + * set) and then write the new register back */ + set_bits = cur3 | FAM17H_MSR_WA_3_BITS; + set_bits |= fam17h_old_3; + custom_wrmsrl_on_cpu(cpu_to_use, FAM17H_MSR_WA_3, set_bits); +} + +/* Grab lock and call into the dynamic workaround function */ +void start_fam17h_zn_dyn_workaround(const int cpu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + int cpu_to_use = cpumask_first(topology_sibling_cpumask(cpu)); +#else + int cpu_to_use = first_cpu(topology_core_siblings(cpu)); +#endif + spinlock_t *cpu_workaround_lock = + per_cpu_ptr(pcpu_workaround_lock, cpu_to_use); + spin_lock(cpu_workaround_lock); + enable_fam17h_zn_dyn_workaround(cpu); + spin_unlock(cpu_workaround_lock); +} + +/* Grab lock and call into the dynamic workaround stopper */ +void stop_fam17h_zn_dyn_workaround(const int cpu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + int cpu_to_use = cpumask_first(topology_sibling_cpumask(cpu)); +#else + int cpu_to_use = first_cpu(topology_core_siblings(cpu)); +#endif + spinlock_t *cpu_workaround_lock = + per_cpu_ptr(pcpu_workaround_lock, cpu_to_use); + spin_lock(cpu_workaround_lock); + disable_fam17h_zn_dyn_workaround(cpu); + spin_unlock(cpu_workaround_lock); +} + +void start_fam17h_zn_static_workaround(const int cpu) +{ + int cpu_to_offline = -1, cpu_to_online = -1; + u64 cur; + + if (!workarounds_started) + { + init_workaround_structs(); + init_workaround_initialize(); + } + + /* Turn on IBS in the CPUID chain. We want to do this per-thread + * because the MSR that overrides CPUID defaults is per-thread. */ + cur = custom_rdmsrl_on_cpu(cpu, CPUID_EXT_FEATURES); + cur |= (1ULL << 42); /* Enable IBS in CPUID */ + custom_wrmsrl_on_cpu(cpu, CPUID_EXT_FEATURES, cur); + + /* Our workaround is per-core, so we should only set this stuff + * once. If we have already done the workaround in one of the + * threads (or from this thread at another time), then we can + * skip the rest of this work. + * This will also prevent us from trying to perform cpu_down + * on any neighboring cores while we are in a hotplug transition. + * (which would cause a deadlock). If we are hotplugging the + * first thread on this core, we will not have any siblings in + * the for_each_cpu() loop below. If we are hotplugging the second + * thread, the first thread would have already set the + * workaround bits. */ + cur = custom_rdmsrl_on_cpu(cpu, FAM17H_MSR_WA_2); + if (cur & FAM17H_MSR_WA_2_BITS) + return; + + /* Disable any neighboring cores while we perform the following work. + * Otherwise, we can lock up the core because of SMT work going on. */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + /* for_each_cpu() was in 2.6.28, but not topology_sibilng_cpumask() */ + for_each_cpu(cpu_to_offline, topology_sibling_cpumask(cpu)) +#else + for_each_cpu_mask(cpu_to_offline, topology_core_siblings(cpu)) +#endif + { + if (cpu_to_offline != cpu) + { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) + remove_cpu(cpu_to_offline); +#else + cpu_down(cpu_to_offline); +#endif + cpu_to_online = cpu_to_offline; + } + } + /* We want to turn on some bits on each physical core when we enable + * the driver, or if that core comes up after we enable the driver. */ + custom_wrmsrl_on_cpu(cpu, FAM17H_MSR_WA_2, (cur | FAM17H_MSR_WA_2_BITS)); + if (cpu_to_online != -1) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) + add_cpu(cpu_to_online); +#else + cpu_up(cpu_to_online); +#endif + } +} + +void stop_fam17h_zn_static_workaround(const int cpu) +{ + unsigned int cpu_to_use; + u64 cur; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + cpu_to_use = cpumask_first(topology_sibling_cpumask(cpu)); +#else + cpu_to_use = first_cpu(topology_core_siblings(cpu)); +#endif + /* Our static workarounds are per-core, not per-thread, so we + * only want to unset the workaround once per core. */ + if (cpu_to_use == cpu) + { + /* Turn off bits on each core when we disable the driver. */ + cur = custom_rdmsrl_on_cpu(cpu_to_use, FAM17H_MSR_WA_2); + /* Unset the bits */ + cur = fam17h_old_2 | (cur & ~FAM17H_MSR_WA_2_BITS); + custom_wrmsrl_on_cpu(cpu, FAM17H_MSR_WA_2, cur); + } + + /* Turn off IBS in the CPUID chain. It's OK to do this without checking + * what it was when we started, because we wouldn't *be* in this + * workaround function if it was on when we started. */ + cur = custom_rdmsrl_on_cpu(cpu, CPUID_EXT_FEATURES); + cur &= ~(1ULL << 42); /* Enable IBS in CPUID */ + custom_wrmsrl_on_cpu(cpu, CPUID_EXT_FEATURES, cur); +} + +void do_fam10h_workaround_420(const int cpu) +{ + __u64 old_op_ctl; + rdmsrl(MSR_IBS_OP_CTL, old_op_ctl); + + /* Within the two writes provided by the workaround an interrupt may + * occur, but the driver could not understand it is intended + * for ibs and leaves it unhandled. This would result in an unknown NMI. + * Mostly this is not a problem, but in some systems, there may + * be some mechanism such as the NMI watchdog which catches the interrupt + * and does operations (even system reboot) thinking it is due to + * an unexpected hardware behaviour. + * To tackle such a problem, we set the IBS_OP_VAL bit so that, if + * an interrupt is generated because of the first write the driver can catch + * it. The driver gets this is a workaround side effect when looking at the + * IBS_OP_CTL and sees the IBS_OP_VAL bit set and the IBS_OP_MAX_CNT(_OLD) + * is equal to 0. In that case, the driver informs the system the interrupt + * has been handled, but it doesn't restart the ibs logic. The last write + * resets the IBS_OP_CTL. */ + old_op_ctl = (old_op_ctl | IBS_OP_VAL) & (~ IBS_OP_MAX_CNT_OLD); + custom_wrmsrl_on_cpu(cpu, MSR_IBS_OP_CTL, old_op_ctl); +} + +``` + +`driver/ibs-workarounds.h`: + +```h +/* + * Linux kernel driver for the AMD Research IBS Toolkit + * + * Copyright (C) 2017-2018 Advanced Micro Devices, Inc. + * + * This driver is available under the Linux kernel's version of the GPLv2. + * See driver/LICENSE for more licensing details. + * + * This file contains a series of workarounds for various generations of AMD + * CPUs. This prevents the main driver code from being polluted by details of + * the main IBS driver. + */ +#ifndef IBS_WORKAROUND_H +#define IBS_WORKAROUND_H + +/* Call these two functions during the single-threaded driver initializatio + * so that we can define, create, and initialize any data structures needed + * for any of our workarounds. + * We don't know which of these workarounds are needed yet (because that + * requires information about which CPU we're running on, so this will + * initialize all of the needed structures early on. */ +/* init_workaround_structs() will return -1 if it could not create the required + * data structures. */ +int init_workaround_structs(void); +void free_workaround_structs(void); +void init_workaround_initialize(void); + +/* Family 10h Erratum #420: Instruction-Based Sampling Engine May Generate + * Interrupt that Cannot Be Cleared. + * Workaround for Fam. 10h Erratum 420 is to first set IbsOpMaxCnt to + * 0 without unsetting IbsOpEn. *Then* clearning IbsOpEn. + * This function sets IbsOpMaxCnt to zero. */ +void do_fam10h_workaround_420(const int cpu); + +/* Family 17h processors with first-generation CPUs (previously code-named + * "Zen") do not necessarily enable IBS by default. + * They require setting some bits in each core to run IBS. + * This can be done with a BIOS setting on many boards, but we run the same + * settings in this driver to increase compatibility */ +/* We need to have locks to prevent IBS Fetch and Op devices from smashing + * on the same registers at the same time. This sets them up when creating + * either device. The start function will enable the workaround if this is + * the first device on this core. The stop function will disable the + * workaround if this is the last device on this core. */ +void start_fam17h_zn_dyn_workaround(const int cpu); +void stop_fam17h_zn_dyn_workaround(const int cpu); + +/* The following functions should only be called when you are starting the + * driver up and stopping the driver. These workarounds must be enabled + * the entire time the driver runs when on a Family 17h processor with a + * first-generation CPU (previously code-named "Zen"). + * You may also want to call the start function whenever bringing up + * a new core that was down when starting the driver. */ +void start_fam17h_zn_static_workaround(const int cpu); +void stop_fam17h_zn_static_workaround(const int cpu); + +#endif /* IBS_WORKAROUND_H */ + +``` + +`driver/install_ibs_driver.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under the Linux kernel's version of the GPLv2 +# See driver/LICENSE for more licensing details. +BASE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +if [ ! -e /dev/cpu/0/ibs/op ] +then + if [ ! -f ${BASE_DIR}/ibs.ko ]; then + echo -e "${BASE_DIR}/ibs.ko does not exist. Trying to build it." + cd ${BASE_DIR} + make + if [ $? -ne 0 ]; then + echo -e "Failed to build. Quitting." + exit -1 + fi + fi + sudo /sbin/insmod ${BASE_DIR}/ibs.ko + + # Set all of the IBS devices accessible by anyone. + # Note that you should change this if you care about security! + CORES=$(cat /proc/cpuinfo | grep processor | awk '{print $3}') + for i in ${CORES}; + do + # We are on some old kernel where the driver cannot create the + # IBS stuff inside /dev/cpu. Manually do this with the installer. + if [ -e /dev/ibs_${i}_op ] + then + mkdir /dev/cpu/${i}/ibs/ + ln -s /dev/ibs_${i}_op /dev/cpu/${i}/ibs/op + ln -s /dev/ibs_${i}_fetch /dev/cpu/${i}/ibs/fetch + fi + # Some older kernels don't properly handle setting of DEVMODE + # permissions, so let's make the driver RW forcibly + if [ -e /dev/cpu/${i}/ibs/op ] + then + sudo chmod g+rw /dev/cpu/${i}/ibs/op + sudo chmod o+rw /dev/cpu/${i}/ibs/op + fi + if [ -e /dev/cpu/${i}/ibs/fetch ] + then + sudo chmod g+rw /dev/cpu/${i}/ibs/fetch + sudo chmod o+rw /dev/cpu/${i}/ibs/fetch + fi + done +else + echo -e "/dev/cpu/0/ibs/op exists, so it looks like the IBS module is already installed." + exit 0 +fi + +``` + +`driver/remove_ibs_driver.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under the Linux kernel's version of the GPLv2 +# See driver/LICENSE for more licensing details. +if [ -e /dev/cpu/0/ibs/op ] +then + if [ -e /dev/ibs_0_op ] + then + which nproc &> /dev/null + if [ $? -eq 0 ]; then + NUM_CORES=$(expr `nproc` - 1) + else + NUM_CORES=$(cat /proc/cpuinfo | grep processor | wc -l) + fi + for i in $(seq 0 $NUM_CORES); + do + rm -rf /dev/cpu/${i}/ibs/ + done + fi + sudo /sbin/rmmod ibs +else + echo -e "It does not look like the IBS driver is installed." + echo -e "Can't find /dev/cpu/0/ibs/op, so skipping removal." + exit 0 +fi + +``` + +`ibs_with_perf_events.txt`: + +```txt +Gathering IBS Samples Using the Linux(r) perf_events System +=============================================================================== + +This document describes the steps needed to collect Instruction Based Sampling +(IBS) traces from AMD processors using the Linux(r) perf_events (a.k.a. 'perf') +system and associated utilities. This document is included as part of AMD +Research's IBS Toolkit; while the AMD Research IBS Toolkit contains its own +driver that allows access to IBS samples, it is an unofficial mechanism that +will not be upstreamed. + +Official support for performance monitoring mechanisms like AMD IBS is offered +as part of the Linux kernel's 'perf' infrastructure (frequently referred to as +perf_events to make it easier to find with an internet search). IBS support +was added in the Linux 3.2 timeframe. Because perf is an official part of the +Linux kernel, any tools built to use IBS should strive to use it if they +plan on being widely distributed. + +That said, the AMD Research IBS Toolkit is available for a number of research- +related IBS development purposes: + * The AMD Research IBS Toolkit driver works on Linux kernels before v3.2, + when IBS support was officially added to perf_events. The earliest kernel + tested is a later CentOS version of 2.6.18 (2.6.18-419, specifically). + * Because the AMD Research IBS Toolkit driver is a module, it is easier + to make custom modifications (such as only gathering some IBS values + in the kernel to reduce overheads). In addition, it can be updated more + rapidly to enable IBS on new processors without upgrading the rest of + the kernel. + * The interface to the AMD Research IBS Toolkit driver may be somewhat + easier to navigate than the perf_events interface (though this file + is an attempt to ameliorate these difficulties). Thus this toolkit may + be useful for researchers who want to use IBS without becoming experts + on perf_events. + +That said, there are major benefits to using perf_events that should not be +discounted: + * It is the official Linux mechanism for accessing IBS data. This point + should not be understated -- because perf_events is the Linux kernel's + mechanism for getting to IBS, drivers like those available in the + AMD Research IBS Toolkit will not be upstreamed. As such, any software + you ship that uses IBS would likely want to use the perf_events + infrastructure rather than shipping this driver with your tool. + * Because perf_events is the official mechanism to access IBS in Linux, + AMD offers no claim of support or warranty for the AMD Research IBS + Toolkit. This toolkit is offered as a snapshot of things that researchers + in one organization of AMD have found useful; it may or may not be + updated as IBS mechanisms change, new processors are added, bugs are + found, or Linux kernel APIs change. + * The perf_events subsystem has all of the capabilities of the AMD + Research IBS Toolkit driver and more. Later in this document, we will + include a translation table from our driver's ioctl() commands to the + commands to do these things in perf_events. In addition, perf_events + has added capabilities, like time-multiplexing IBS samples between + separate processes (while this toolkit's IBS samples can only be + gathered system wide and must be post-processed to remove samples from + other processes). + * Using the perf_events subsystem to gather IBS traces also allows you to + take advantage of perf_events's user-land tools (such as 'perf'). These + can be used to map IBS samples onto processes, graph data about samples, + and many other useful performance-monitoring tasks. We have replicated + some of this functionality in the AMD Research IBS Toolkit, but we do + not have any plans to reach parity with this tooling (the goal with + our toolkit was, instead, to make prototyping new tooling in a research + setting somewhat easier). + +With these points in mind, our view of the AMD Research IBS Toolkit is as a +research and prototyping platform. You can use it to bring up new hardware, +try out new IBS sampling mechanisms, gather IBS samples for research +projects, and quickly build new IBS-using tools. This document exists as a +sort of Rosetta Stone for taking those tools and translating them to work +on top of the official perf_events mechanisms once you want to release +the tool for general use. + +This document is thus split into 6 parts: + 1. A quick introduction to using Linux perf_events to obtain IBS samples + 2. Directions for opening the perf_event file descriptor for IBS samples + 3. How to map the kernel ring buffer in user land to obtain IBS samples + 4. How to start IBS sample collection in perf_events + 5. Collecting IBS samples out of the perf_events system + 6. A map of AMD Research IBS Toolkit commands to their perf_event mirrors + + +An introduction to obtaining IBS samples with the Linux(r) perf_events system +=============================================================================== +The Linux(r) perf_events system (officially 'perf') is the name for both the +kernel-level and user-level tools to gather hardware performance monitoring +information in Linux. For general information about this system, please see +the following websites: + * http://web.eece.maine.edu/~vweaver/projects/perf_events/ + * http://www.brendangregg.com/perf.html + * https://perf.wiki.kernel.org/index.php/Main_Page + +In addition, a great deal of information about how to interface with the +kernel perf infrastructure can be found in the manpage for perf_event_open. +See, for example: http://man7.org/linux/man-pages/man2/perf_event_open.2.html + +The perf_events system can be used for a wide range of performance monitoring +hardware, from generic performance counters to more advanced vendor-specific +hardware. AMD's IBS support is one part of this, and this document only +covers the information necessary to gather this data. + +At high level, there are six steps needed to gather IBS data from perf_events: + 1. A file descriptor for an IBS event must be opened using perf_event_open() + system call with proper attributes. + 2. Raw IBS samples are collected by the kernel in a kernel ring buffer. + Therefore, the user must mmap that ring buffer to user-land by calling + the mmap() system on the file descriptor for the IBS event. + 3. The event must then be enabled to start collecting the samples. For + example, this could be done just before starting the execution of the + program which is to be monitored. + When IBS monitoring is on the samples are collected in the kernel ring + buffer by the Linux kernel. + 4. Samples collected in the kernel ring buffer must be read to user space + for post-processing and/or parsing. This reading of the ring buffer can + also take place while more values are being added to the buffer. + 5. Parsing the IBS samples. + 6. Finally, when monitoring is finished, the event (IBS sampling) must be + disabled. + +The Linux perf_events system comes with several user-land utility functions +that help users interact with perf's interface. While it is not strictly +necessary to use them, they can be helpful in reducing the amount of effort +needed to collect raw IBS samples. + +The following sections describe more details of these four steps of gathering +IBS data from the perf_events system. + + +Directions for opening the perf_event file descriptor for IBS samples +=============================================================================== +The Linux(r) perf_events subsystem exposes a narrow interface to user land for +performance monitoring. Specifically, any event that is to be monitored must +open a file descriptor with perf_event_open() system call. The manpage for +perf_event_open() contains a great deal of information about this interface, +but this and the following sections primarily discuss how to use this interface +to get IBS samples. + +The contents of the perf_event_attr structure passed as a parameter to the +perf_event_open() system call determines which event should be monitored. +The code snippet at the bottom of this section shows example content of various +fields of this structure to enable IBS. Since IBS samples can be collected for +micro-ops or for fetch samples (see this tool's README.txt for more +information), there are two separate code snippets. The first shows example +attribute values for IBS micro-op sampling, while the second shows the +attribute values for IBS fetch sampling. + +-------------------------------------------------------------------------- +Code snippet 1: perf_event_attr fields required for IBS op sampling +-------------------------------------------------------------------------- +//Type comes from /sys/bus/event_source/devices/ibs_op/type +type = 8; +// Setting this bit in config enables sampling every sample_period ops. +// Leaving it unset will take an IBS sample every sample_period cycles +config = ((1ULL<<19); +sample_period = 0x10; // Can be any value > 0x10 +sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_CPU; +disabled = 1; +inherit = 1; +execlude_kernel = 0; +exclude_user = 0; +exclude_hv = 0; +exclude_guest = 0; +exclude_idle = 0; +exclude_host = 0; +pinned = 0; +pecise_ip = 1; +mmap = 1; +comm = 1; +task = 1; +sample_id_all = 1; +comm_exec = 1; +read_format = 0; +mmap_pages= 256; //This can be changed – user settable +-------------------------------------------------------------------------- + +-------------------------------------------------------------------------- +Code snippet 2: perf_event_attr fields required for IBS fetch sampling +-------------------------------------------------------------------------- +//Type from /sys/bus/event_source/devices/ibs_fetch/type +type = 9; +// Set bit 57 to cause the low order bits of the sample period to randomize +config = ((1ULL<<57); +// Set the number of completed fetches between samples +sample_period = 0x10; // Can be any value > 0x10 +sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_CPU; +disabled = 1; +inherit = 1; +execlude_kernel = 0; +exclude_user = 0; +exclude_hv = 0; +exclude_guest = 0; +exclude_idle = 0; +exclude_host = 0; +pinned = 0; +pecise_ip = 1; +mmap = 1; +comm = 1; +task = 1; +sample_id_all = 1; +comm_exec = 1; +read_format = 0; +mmap_pages= 256; //This can be changed – user settable +-------------------------------------------------------------------------- + +A few of the fields in these code snippets demand special attention. + +1. type + This identifies the event to be opened. To gather IBS fetch or op events, + the value in this field must be set to a dynamic value (and thus it can + change from system to system). + To gather IBS op samples, the value to set this to can be found in: + /sys/bus/event_source/devices/ibs_op/type + To gather IBS fetch samples, the value to set this to can be found in: + /sys/bus/event_source/devices/ibs_fetch/type. +2. config + This field should be set according to the type of IBS monitoring + and the configuration MSR values the user wants. + For instance, the control MSR for IBS fetch (defined in the AMD BKDG + or PPR manuals -- MSRC001_1030) says bit 57 will cause the low 4 bits + of the fetch sample rate to be randomized by hardware. As such, setting + bit 57 of the config field will enable this feature. + Details of how this is used can be found in the Linux kernel source at: + arch/x86/events/amd/ibs.c +3. sample_period + This field defines how often IBS samples are taken. IBS fetch and op + sampling is performed differently (as defined in AMD's manuals). + IBS fetch samples are taken once every sample_period completed fetches. + In other words, after sample_period fetches successfully send some bytes + to the decoder, the next fetch is taken as an IBS sample. + IBS op samples can either count the number of cycles since the last sample, + or they can be configured (with config bit 19) to count the number of + ops issued between each sample. + In either case, this field holds the number of {units} between each IBS + sample. For both IBS op and fetch sampling, the hardware will ignore + requests to set the low 4 bits -- as such, the user must set this field + to at least 0x10. +4. sample_type + This is normally used to tell perf_events what type of samples to take. + Since we would like to collect RAW samples from IBS, set this to + PERF_SAMPLE_RAW | PERF_SAMPLE_CPU. +5. mmap_pages + This parameter is used to set the size of the kernel ring buffer used to + hold the IBS samples. This must be a power of two. Setting this to a + lower values runs the risk of dropping some IBS samples before user-space + code can read them; increasing it takes more memory space. +6. misc fields + Currently, the IBS mechanism in Linux's perf_events subsystem does not + allow exclude_kernel, exclude_host, exclude_guest, exclude_idle, exclude_hv + or exclude_idle. As such, these should be set to zero. + +While it is possible manually invoke the perf_event_open() system call with the +attribute values mentioned above, the Linux perf_events subsystem comes with a +collection of helpful utility functions to do this work as well. +We suggest that a user can utilize perf_evsel__open() and perf_evfunction() +functions. See the Linux source code in tools/perf/util/evsel.{h/c}. These +functions are likely to be easier to use than directly calling +perf_event_open(). They take the same perf_event attribute structures that are +described above. + + +How to map the kernel ring buffer in user land to obtain IBS samples +=============================================================================== +Once IBS sampling is configured (see above) and enabled (see below), the raw +IBS samples will be collected into a kernel ring buffer (see "mmap_pages" above +for how to set the size of this ring buffer). + +To access these samples from a user-land application, this kernel ring buffer +must be mapped to user land. This could be done by mmap()-ing the file +descriptor obtained by perf_event_open. + +The first page in the kernel buffer contains metadata. Thus the size of this +mmap() needs to be equal to the size of kernel ring buffer set while opening +the event (the value in the "mmap_pages" field in the attribute) + 1. + +However, there are utility functions to make life easier for the purpose as +well. The relevant utility function is perf_evlist__mmap(). +(defined in the Linux(r) source code at tools/perf/util/evsel.h/c) + + +How to start IBS sample collection in perf_events +=============================================================================== +With the IBS sampling mechanism configured, and the storage location for the +IBS samples mmap()-ed to allow your user-space application to read it, the next +step is to enable IBS sampling through an ioctl() system call on the IBS file +descriptor (returned from perf_event_open) with the command: +PERF_EVENT_IOC_ENABLE + +However, as with other steps, there is a helper function to do this: +perf_evlist__enable(). +After IBS sampling is enabled, the application to be monitored can be launched. + + +Collecting IBS samples out of the perf_events system +=============================================================================== +After the IBS sampling mechanism beings creating samples, the user should read +them out of the ring buffer for processing. There are utility functions in +perf to help with this: perf_evlist__mmap_read(). This helper function can be +called in a loop (until it returns NULL) to read the raw samples out of the +ring buffer once at a time. Another utility function, +perf_evlist__mmap_consume(), should be called at the end of each iteration +of this loop to update the head and tail pointers to the kernel ring buffer. + +Each call to the perf_evlist__mmap_read() returns an object of a union called +perf_event (defined in the Linux(r) source code: tools/perf/util/event.h). +When no more samples remain perf_evlist__mmap_read() returns NULL. + +Each raw perf_event sample can then be parsed using an utility function +called perf_evlist__parse_sample(). This utility function takes a perf_event +object returned by the perf_evlist__mmap_read() and parses it to structure +perf_sample (defined tools/perf/util/event.h). + + +Parsing raw IBS samples +=============================================================================== +The perf_sample data structure will have a "void *raw_data" -- this points +to a region of memory that contains the concatenated values from all of the +IBS registers associated with this sample. Because this is a raw sample, it is +the user's responsibility to parse this series of bytes and make sense of them. + +There are two places to look for this information: first, the BKDG or PPR +manuals for your processor will define what the bytes within each register +mean. For example, the PPR for Family 17h Model 01h (available at: +https://support.amd.com/TechDocs/54945_PPR_Family_17h_Models_00h-0Fh.pdf) shows +that bit 58 of MSRC001_1030 is whether this IBS op sample was an L2 cache miss. + +The raw_data pointer points to an array of all of the MSRs associated with +this type of IBS sample. The number of registers stored, and the order they are +stored in, depends both on the hardware in use as well as the version of the +Linux(r) kernel in use at the time. The length of the raw_data array is +available in the perf_sample field "raw_size". + +The order that these registers are stored depends on the function that stores +the values into the raw_data, which can be found in the Linux IBS source +code. As of Linux 4.11, this is located in the function perf_ibs_handle_irq() +in the file arch/x86/events/amd/ibs.c. You can see each time one of the MSR +values is read into "buf" -- this is the data available in the raw_data array. + +For example (all line numbers taken from Linux 4.11 ibs.c): +Both IBS op and fetch samples begin by reading their configuration register +(IBS_FETCH_CTL or IBS_OP_CTL) into the buffer (ibs.c:607). + +Then, in a loop at ibs.c:628, most of the remaining registers for this +type of sample are read into the buffer. They are read in numerical order, +starting from the register immediately after the CTL register, through +the last register that all AMD processors that support IBS can use. +The register numbers being iterated through can be found at: +arch/x86/include/asm/msr-index.h lines 299-310. + +Finally, only some AMD processors support the branch target and OPDATA4 MSRs, +so these are only stored out if the CPU supports them. + + +Disabling IBS sampling +=============================================================================== +Once the application is over (or you desire to stop taking IBS samples), the +perf_events IBS sampling can be disabled using an ioctl() system call in a +similar manner to enabling it: use the IOCTL command PERF_EVENT_IOC_DISABLE +The utility function perf_evlist__disable() can be used instead. + + +A map of AMD Research IBS Toolkit commands to their perf_event mirrors. +=============================================================================== +The primary way to interact with the AMD Research IBS Toolkit driver is through +ioctl() commands, which are defined in include/ibs-uapi.h. The AMD driver does +not allow reads with mmap(), but instead allows polling and reading directly +from a file descriptor. + +Perhaps most importantly, the AMD Research IBS Toolkit enables IBS on a +per-processor basis (i.e. it is hardware oriented) rather than a per-process +(i.e. software oriented) basis. As such, the file descriptors and ioctl() +operations are associated with a CPU, and any process that runs on that CPU +while IBS is enabled will have samples taken. It is the user's responsibility +to look for samples of interest. The Linux(r) perf_events IBS sampling +mechanism is per-process, and will follow that process as it migrates around +cores. + +Because the mechanisms for interacting with these two IBS systems are somewhat +different, this section describes a mapping between the AMD Research IBS +Toolkit calls and the way to perform similar types of commands in the +Linux(r) perf_events subsystem. This should help you port any user-land +software that is initially designed to use the AMD Research IBS Toolkit +to instead use perf_events. + +1. IBS Setup and Other ioctl() commands +| AMD Research IBS Toolkit | Linux perf_events | +-------------------------------------------------------------------------------- +| ioctl(IBS_ENABLE) | ioctl(PERF_EVENT_IOC_ENABLE) | +| | Plus the configuration described | +| | for perf_event_attr must be done. | +| ioctl(IBS_DISABLE) | ioctl(PERF_EVENT_IOC_DISABLE) | +| ioctl(SET_CUR_CNT)/ioctl(SET_CNT) | Not used, always set to 0 | +| ioctl(GET_CUR_CNT)/ioctl(GET_CNT) | Not needed, value is always 0 | +| ioctl(SET_MAX_CNT) | perf_event_attr.sample_period | +| | perf_events values are exact sample | +| | rate; toolkit values are upper bits | +| | only (ignoring bottom 4 bits) | +| ioctl(GET_MAX_CNT) | Not available (read perf_event_attr) | +| ioctl(SET_CNT_CTL) | perf_event_attr.config, set bit 19 | +| ioctl(GET_CNT_CTL) | Not available (read perf_event_attr) | +| ioctl(SET_RAND_EN) | perf_event_attr.config set bit 57 | +| ioctl(GET_RAND_EN) | Not available (read perf_event_attr) | +| ioctl(SET_BUFFER_SIZE) | perf_event_attr.mmap_pages | +| ioctl(GET_BUFFER_SIZE) | Not available (read perf_event_attr) | +| ioctl(SET_POLL_SIZE) | perf_event_attr.wakeup_events | +| | or perf_event_attr.wakeup_watermark | +| | The latter is only in bytes. | +| ioctl(GET_POLL_SIZE) | Not available (read perf_event_attr) | +| ioctl(GET_LOST) | Not available | +| ioctl(DEBUG_BUFFER) | Not available | +| ioctl(RESET_BUFFER) | ioctl(PERF_EVENT_IOC_RESET) | +| ioctl(FIONREAD) | Not available (not needead, read | +| | the mmapped region using | +| | perf_evlist_mmap_read() in a loop | +| | until it returns NULL.) | +-------------------------------------------------------------------------------- + +2. Reading IBS samples +| AMD Research IBS Toolkit | Linux perf_events | +-------------------------------------------------------------------------------- +| fopen a core's IBS file for later | perf_evlist__mmap() on evlist | +| reading, from: | containing IBS perf_event | +| /dev/cpu//ibs/op | | +| /dev/cpu//ibs/fetch | | +| fread from core's IBS open fd | perf_evlist__mmap_read() on evlist | +| Poll until data is available using | perf_evlist__poll() on evlist | +| poll() or select() on fd from | | +| opening core's op or fetch file | | +-------------------------------------------------------------------------------- + + +Trademark Attribution +=============================================================================== +(c) 2017-2018 Advanced Micro Devices, Inc. All rights reserved. +AMD, the AMD Arrow logo, and combinations thereof are trademarks of +Advanced Micro Devices, Inc. in the United States and/or other jurisdictions. +Linux is a registered trademark of Linus Torvalds. +Other names are for informational purposes only and may be trademarks of their +respective owners. + +``` + +`include/LICENSE.bsd`: + +```bsd +Copyright (c) 2015-2017 Advanced Micro Devices, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. The names of its contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +``` + +`include/LICENSE.gpl`: + +```gpl +This software is Copyright (c) 2015-2017 Advanced Micro Devices, Inc. + +---------------------------------------- + + NOTE! This copyright does *not* cover user programs that use kernel + services by normal system calls - this is merely considered normal use + of the kernel, and does *not* fall under the heading of "derived work". + Also note that the GPL below is copyrighted by the Free Software + Foundation, but the instance of code that it refers to (the linux + kernel) is copyrighted by me and others who actually wrote it. + + Linus Torvalds + +---------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +``` + +`include/ibs-msr-index.h`: + +```h +/* + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This file contains the MSR numbers, bits and masks for AMD IBS data + * This file is distributed under the BSD license described in + * include/LICENSE.bsd + * Alternatively, this file may be distributed under the terms of the + * Linux kernel's version of the GPLv2. See include/LICENSE.gpl + * + * + * Macros defined here represent register numbers and bit masks associated with + * IBS. The numbers (and names) are derived from the various AMD BKDG manuals. + * + * Because various families of processors support different sets of IBS + * IBS data, support for a limited family of processors is marked in comments. + * + * Decoder for processor names: + * No marking: Supported since Family 10h + * LN: Llano, Family 12h + * BD: Bulldozer, Family 15h Models 00h-0Fh + * TN: Trinity, Family 15h Models 10h-1Fh + * KV: Kaveri, Family 15h Models 30h-3Fh + * CZ: Carrizo, Family 15h Models 60h-6Fh + * ST: Stoney Ridge, Family 15h Models 70h-7Fh + * BT: Bobcat, Family 16h Models 00h-0Fh + * JG: Jaguar, Family 16h Models 30h-3Fh + * ZN: Zen, Family 17h Models 00h-0Fh + * + */ + +#ifndef IBS_MSR_INDEX_H +#define IBS_MSR_INDEX_H + +/* + * Bits and masks by register + */ + +#define MSR_IBS_FETCH_CTL 0xc0011030 +#define IBS_RAND_EN (1ULL<<57) +#define IBS_L2_TLB_MISS (1ULL<<56) +#define IBS_L1_TLB_MISS (1ULL<<55) +#define IBS_L1_TLB_PG_SZ (3ULL<<53) +#define IBS_PHY_ADDR_VALID (1ULL<<52) +#define IBS_IC_MISS (1ULL<<51) +#define IBS_FETCH_COMP (1ULL<<50) +#define IBS_FETCH_VAL (1ULL<<49) +#define IBS_FETCH_EN (1ULL<<48) +#define IBS_FETCH_LAT (0xffffULL<<32) +#define IBS_FETCH_CNT (0xffffULL<<16) +#define IBS_FETCH_MAX_CNT 0xffffULL + +#define MSR_IBS_FETCH_LIN_AD 0xc0011031 +#define IBS_FETCH_LIN_AD (~0ULL) + +#define MSR_IBS_FETCH_PHYS_AD 0xc0011032 +#define IBS_FETCH_PHYS_AD (~0ULL) + +#define MSR_IBS_OP_CTL 0xc0011033 +#define IBS_OP_CUR_CNT (0x7ffffffULL<<32) /* LN+ */ +#define IBS_OP_CUR_CNT_OLD (0XfffffULL<<32) /* Family 10h only */ +/* Alternate mask excludes bits that are randomized by software */ +#define IBS_OP_CUR_CNT_23 (0x7fffff0ULL<<32) +#define IBS_OP_MAX_CNT (0x7f0ffffULL) /* LN+ */ +#define IBS_OP_MAX_CNT_OLD (0xffffULL) /* Family 10h only */ +#define IBS_OP_CNT_CTL (1ULL<<19) +#define IBS_OP_VAL (1ULL<<18) +#define IBS_OP_EN (1ULL<<17) + +#define MSR_IBS_OP_RIP 0xc0011034 +#define IBS_OP_RIP (~0ULL) + +#define MSR_IBS_OP_DATA 0xc0011035 +#define IBS_OP_MICROCODE (1ULL<<40) /* KV+, BT+ */ +#define IBS_OP_BRN_FUSE (1ULL<<39) /* KV+, BT+ */ +#define IBS_RIP_INVALID (1ULL<<38) /* LN+ */ +#define IBS_OP_BRN_RET (1ULL<<37) +#define IBS_OP_BRN_MISP (1ULL<<36) +#define IBS_OP_BRN_TAKEN (1ULL<<35) +#define IBS_OP_RETURN (1ULL<<34) +#define IBS_OP_MISP_RETURN (1ULL<<33) /* Fam. 10h, LN, BD only */ +#define IBS_OP_BRN_RESYNC (1ULL<<32) /* Fam. 10h, LN, BD only */ +#define IBS_TAG_TO_RET_CTR (0xffffULL<<16) +#define IBS_COMP_TO_RET_CTR 0xffffULL + +#define MSR_IBS_OP_DATA2 0xc0011036 +#define NB_IBS_REQ_CACHE_HIT_ST (1ULL<<5) /* Not valid in BT, JG */ +#define NB_IBS_REQ_DST_NODE (1ULL<<4) /* Not valid in BT, JG */ +#define NB_IBS_REQ_SRC 7ULL + +#define MSR_IBS_OP_DATA3 0xc0011037 +#define IBS_TLB_REFILL_LAT (0xffffULL<<48) /* KV+, BT+ */ +#define IBS_DC_MISS_LAT (0xffffULL<<32) +#define IBS_OP_DC_MISS_OPEN_MEM_REQS (0x3fULL<<26) /* KV+, BT+ */ +#define IBS_OP_MEM_WIDTH (0xfULL<<22) /* KV+, BT+ */ +#define IBS_SW_PF (1ULL<<21) /* KV+, BT+ */ +#define IBS_L2_MISS (1ULL<<20) /* KV+, BT+ */ +#define IBS_DC_L2_TLB_HIT_1G (1ULL<<19) +#define IBS_DC_PHY_ADDR_VALID (1ULL<<18) +#define IBS_DC_LIN_ADDR_VALID (1ULL<<17) +#define DC_MISS_NO_MAB_ALLOC (1ULL<<16) /* KV+, BT+ only */ +#define IBS_DC_MAB_HIT (1ULL<<16) /* Fam. 10h-TN, */ +#define IBS_DC_LOCKED_OP (1ULL<<15) +#define IBS_DC_UC_MEM_ACC (1ULL<<14) +#define IBS_DC_WC_MEM_ACC (1ULL<<13) +#define IBS_DC_ST_TO_LD_CAN (1ULL<<12) /* Fam. 10h only */ +#define IBS_DC_ST_TO_LD_FWD (1ULL<<11) /* Fam. 10h only */ +#define IBS_DC_ST_BNK_CON (1ULL<<10) /* Fam. 10h only */ +#define IBS_DC_LD_BNK_CON (1ULL<<9) /* Fam. 10h only */ +#define IBS_DC_MIS_ACC (1ULL<<8) +#define IBS_DC_MISS (1ULL<<7) +#define IBS_DC_L2_TLB_HIT_2M (1ULL<<6) +#define IBS_DC_L1_TLB_HIT_1G (1ULL<<5) +#define IBS_DC_L1_TLB_HIT_2M (1ULL<<4) +#define IBS_DC_L2_TLB_MISS (1ULL<<3) +#define IBS_DC_L1_TLB_MISS (1ULL<<2) +#define IBS_ST_OP (1ULL<<1) +#define IBS_LD_OP 1ULL + +#define MSR_IBS_DC_LIN_AD 0xc0011038 +#define IBS_DC_LIN_AD (~0ULL) + +#define MSR_IBS_DC_PHYS_AD 0xc0011039 +#define IBS_DC_PHYS_AD 0xffffffffffffULL + +#define MSR_IBS_CONTROL 0xc001103a +#define IBS_LVT_OFFSET_VAL (1ULL<<8) +#define IBS_LVT_OFFSET 0xfULL + +#define MSR_IBS_BR_TARGET 0xc001103b /* LN+ */ +#define IBS_BR_TARGET (~0ULL) /* LN+ */ + +#define MSR_IBS_EXTD_CTL 0xc001103c /* CZ+ */ +#define IBS_ITLB_REFILL_LAT (0xffff) /* CZ+ */ + +#define MSR_IBS_OP_DATA4 0xc001103d /* CZ, ST only */ +#define IBS_OP_LD_RESYNC 1ULL /* CZ, ST only */ + +#endif /* IBS_MSR_INDEX_H */ + +``` + +`include/ibs-uapi.h`: + +```h +/* + * User API header for interfacing with the Linux kernel driver in the AMD + * Research IBS Toolkit. + * + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This file is distributed under the BSD license described in + * include/LICENSE.bsd + * Alternatively, this file may be distributed under the terms of the + * Linux kernel's version of the GPLv2. See include/LICENSE.gpl + * + * + * This file contains the user interface to the driver portion of the AMD + * Research IBS Toolkit. The driver and its code are available in the upper- + * level 'driver' directory. + * This file includes: + * (1) definitions of structs that represent records read from the driver + * and passed to user-level applications. + * (2) definition and documentation of ioctl commands that may be issued + * to the driver from user-space applications + * + */ + +#ifndef IBS_UAPI_H +#define IBS_UAPI_H + +#if !defined(__KERNEL__) && !defined(MODULE) +#include +#include + +// In the future, if we make modifications to the ibs_op_t or ibs_fetch_t +// data structures, raw dumps of these values may be impossible to read. +// As such, we need to version this data structure so that parsing +// applications can read old IBS dumps. +#define IBS_OP_STRUCT_VERSION 1 +#define IBS_FETCH_STRUCT_VERSION 1 + +/* The following unions can be used to pull out specific values from inside of + an IBS sample. */ +typedef union { + uint64_t val; + struct { + uint16_t ibs_op_max_cnt : 16; + uint16_t reserved_1 : 1; + uint16_t ibs_op_en : 1; + uint16_t ibs_op_val : 1; + uint16_t ibs_op_cnt_ctl : 1; + uint16_t ibs_op_max_cnt_upper: 7; + uint16_t reserved_2 : 5; + uint32_t ibs_op_cur_cnt : 27; + uint32_t reserved_3 : 5; + } reg; +} ibs_op_ctl_t; + +typedef union { + uint64_t val; + struct { + uint16_t ibs_comp_to_ret_ctr; + uint16_t ibs_tag_to_ret_ctr; + uint8_t ibs_op_brn_resync : 1; /* Fam. 10h, LN, BD only */ + uint8_t ibs_op_misp_return : 1; /* Fam. 10h, LN, BD only */ + uint8_t ibs_op_return : 1; + uint8_t ibs_op_brn_taken : 1; + uint8_t ibs_op_brn_misp : 1; + uint8_t ibs_op_brn_ret : 1; + uint8_t ibs_rip_invalid : 1; + uint8_t ibs_op_brn_fuse : 1; /* KV+, BT+ */ + uint8_t ibs_op_microcode : 1; /* KV+, BT+ */ + uint32_t reserved : 23; + } reg; +} ibs_op_data1_t; + +typedef union { + uint64_t val; + struct { + uint8_t ibs_nb_req_src : 3; + uint8_t reserved_1 : 1; + uint8_t ibs_nb_req_dst_node : 1; /* Not valid in BT, JG */ + uint8_t ibs_nb_req_cache_hit_st : 1; /* Not valid in BT, JG */ + uint64_t reserved_2 : 58; + } reg; +} ibs_op_data2_t; + +typedef union { + uint64_t val; + struct { + uint8_t ibs_ld_op : 1; + uint8_t ibs_st_op : 1; + uint8_t ibs_dc_l1_tlb_miss : 1; + uint8_t ibs_dc_l2_tlb_miss : 1; + uint8_t ibs_dc_l1_tlb_hit_2m : 1; + uint8_t ibs_dc_l1_tlb_hit_1g : 1; + uint8_t ibs_dc_l2_tlb_hit_2m : 1; + uint8_t ibs_dc_miss : 1; + uint8_t ibs_dc_miss_acc : 1; + uint8_t ibs_dc_ld_bank_con : 1; /* Fam. 10h, LN, BD only */ + uint8_t ibs_dc_st_bank_con : 1; /* Fam. 10h, LN only */ + uint8_t ibs_dc_st_to_ld_fwd : 1; /* Fam. 10h, LN, BD, BT+ */ + uint8_t ibs_dc_st_to_ld_can : 1; /* Fam. 10h, LN, BD only */ + uint8_t ibs_dc_wc_mem_acc : 1; + uint8_t ibs_dc_uc_mem_acc : 1; + uint8_t ibs_dc_locked_op : 1; + uint16_t ibs_dc_no_mab_alloc : 1; /* Fam. 10h-TN: + IBS DC MAB hit */ + uint16_t ibs_lin_addr_valid : 1; + uint16_t ibs_phy_addr_valid : 1; + uint16_t ibs_dc_l2_tlb_hit_1g : 1; + uint16_t ibs_l2_miss : 1; /* KV+, BT+ */ + uint16_t ibs_sw_pf : 1; /* KV+, BT+ */ + uint16_t ibs_op_mem_width : 4; /* KV+, BT+ */ + uint16_t ibs_op_dc_miss_open_mem_reqs: 6; /* KV+, BT+ */ + uint16_t ibs_dc_miss_lat; + uint16_t ibs_tlb_refill_lat; /* KV+, BT+ */ + } reg; +} ibs_op_data3_t; + +typedef union { + uint64_t val; + struct { + uint8_t ibs_op_ld_resync: 1; + uint64_t reserved : 63; + } reg; +} ibs_op_data4_t; /* CZ, ST only */ + +typedef union { + uint64_t val; + struct { + uint64_t ibs_dc_phys_addr : 48; + uint64_t reserved : 16; + } reg; +} ibs_op_dc_phys_addr_t; + +// User-space structs that define how we talk to the IBS driver +typedef struct ibs_op { + ibs_op_ctl_t op_ctl; + uint64_t op_rip; + ibs_op_data1_t op_data; + ibs_op_data2_t op_data2; + ibs_op_data3_t op_data3; + ibs_op_data4_t op_data4; + uint64_t dc_lin_ad; + ibs_op_dc_phys_addr_t dc_phys_ad; + uint64_t br_target; + uint64_t tsc; + uint64_t cr3; + int tid; + int pid; + int cpu; + int kern_mode; +} ibs_op_t; +typedef ibs_op_t ibs_op_v1_t; + +typedef union { + uint64_t val; + struct { + uint16_t ibs_fetch_max_cnt; + uint16_t ibs_fetch_cnt; + uint16_t ibs_fetch_lat; + uint8_t ibs_fetch_en : 1; + uint8_t ibs_fetch_val : 1; + uint8_t ibs_fetch_comp : 1; + uint8_t ibs_ic_miss : 1; + uint8_t ibs_phy_addr_valid : 1; + uint8_t ibs_l1_tlb_pg_sz : 2; + uint8_t ibs_l1_tlb_miss : 1; + uint8_t ibs_l2_tlb_miss : 1; + uint8_t ibs_rand_en : 1; + uint8_t ibs_fetch_l2_miss : 1; /* CZ+ */ + uint8_t reserved : 5; + } reg; +} ibs_fetch_ctl_t; + +typedef union { + uint64_t val; + struct { + uint64_t ibs_fetch_phy_addr : 48; + uint64_t reserved : 16; + } reg; +} ibs_fetch_phys_addr; + +typedef union { + uint64_t val; + struct { + uint16_t ibs_itlb_refill_lat; + uint64_t reserved : 48; + } reg; +} ibs_fetch_extd_ctl; /* CZ+ */ + +typedef struct ibs_fetch { + ibs_fetch_ctl_t fetch_ctl; + ibs_fetch_extd_ctl fetch_ctl_extd; + uint64_t fetch_lin_ad; + ibs_fetch_phys_addr fetch_phys_ad; + uint64_t tsc; + uint64_t cr3; + int tid; + int pid; + int cpu; + int kern_mode; +} ibs_fetch_t; +typedef ibs_fetch_t ibs_fetch_v1_t; +#endif + +/** + * DOC: IBS ioctl commands + * + * ENABLE: Activate IBS. + * + * DISABLE: Deactivate IBS. You may still read buffered samples in the + * disabled state. + * + * SET_CUR_CNT: Set the upper 23 bits of the 27-bit IBS op/cycle counter + * start value (the low 4 bits are randomized). Possible values + * satisfy 0 <= CUR_CNT < 2^23. (On IBS fetch devices, this + * command behaves like SET_CNT; see that ioctl for details.) + * + * SET_CNT: Set the upper 16 bits of the 20-bit fetch counter (the low 4 + * bits are randomized). Possible values satisfy 0<= CNT < 2^16 + * *and* CNT <= MAX_CNT (see SET_MAX_CNT ioctl). (On IBS op + * devices, this command behaves like SET_CUR_CNT; see that ioctl + * for details.) + * + * This does nothing on Trinity (and earlier??) processors, on + * which the fetch counter always begins "at the maximum value" + * (see Erratum 719 in Revision Guide for AMD Family 15h Models + * 10-1Fh Processors, Order #48931). + * + * GET_CUR_CNT: Return the counter start value (*not* the current value). + * GET_CNT: Same as above. + * + * SET_MAX_CNT: Valid inputs to this command are slightly different for fetch + * and op IBS flavors. When issued to an IBS *op* device, set the + * upper 23 bits of the 27-bit IBS op/cycle counter + * maximum value (the low 4 bits are always 0). Possible values + * satisfy 9 <= MAX_CNT < 2^23. + * + * When issued to an IBS *fetch* device, set the upper 16 bits of + * the 20-bit fetch counter (the low 4 bits are always zero). + * Possible values satisfy 0<= MAX_CNT < 2^16 *and* CNT <= MAX_CNT + * (see SET_CNT ioctl). + * + * GET_MAX_CNT: Return the counter maximum value. + * + * SET_CNT_CTL: IBS op counter control - count ops or count cycles. Possible + * values are 0 to count cycles and 1 to count ops. Default 1. + * (Not meaningful for fetch devices.) + * + * GET_CNT_CTL: Return the counter control value. (Not meaningful for fetch + * devices.) + * + * SET_RAND_EN: IBS fetch randomization enable. Possible values are 0 to + * disable randomization (low 4 bits are set to 0h upon fetch + * enable), and 1 to enable. Default 1. (Not meaningful for op + * devices.) + * + * GET_RAND_EN: Return the IBS fetch randomization enable value. (Not + * meaningful for op devices.) + * + * GET_LOST: Return the number of IBS samples that were lost because the + * ring buffer used to store the samples was full, and the + * user has not read the values. Reading this resets the counter + * to zero (0). + * + * SET_POLL_SIZE: This sets the minimum number of samples (*not* bytes) that + * must be ready before a call to poll (select, poll, epoll) will + * return, indicating that the device is "ready". Defaults to 1. + * Note that you can still read when the device is not "ready". + * + * This is designed to optimize read efficiency. By reading only + * when there is a substantial amount of data ready, fewer calls + * to read can collect the same amount of data from the device. + * + * This value should be chosen with the buffer capacity in mind. + * Possible values satisfy 0 < POLL_SIZE < capacity (in number of + * entries); any other input sets errno to -EINVAL. + * + * GET_POLL_SIZE: Returns the current POLL_SIZE. + * + * SET_BUFFER_SIZE: Set the size of the IBS sample buffer in number of bytes. + * If the requested buffer size equals the existing buffer size, + * then the buffer is simply cleared; otherwise, the existing + * buffer is freed and a new one of requested size is allocated. + * (If this allocation fails, -ENOMEM is returned.) IBS must be + * disabled. The argument must be at least the size of one buffer + * entry (i.e. the size of one of the structs ibs_op or ibs_fetch + * defined above). + * + * GET_BUFFER_SIZE: Get the size of the IBS sample buffer in number of bytes. + * + * DEBUG_BUFFER: Print information about the buffers to the kernel log. + * + * RESET_BUFFER: Empty the sample buffer, throwing away existing data. + * + * FIONREAD: Returns the number of samples that are immediately available to + * read. This will still work when the driver is disabled, since + * the buffers don't drain until they are fully read or IBS is + * re-enabled. + */ +#define IBS_ENABLE 0x0U +#define IBS_DISABLE 0x1U + +#define SET_CUR_CNT 0x2U +#define GET_CUR_CNT 0x3U +#define SET_CNT 0x4U +#define GET_CNT 0x5U +#define SET_MAX_CNT 0x6U +#define GET_MAX_CNT 0x7U +#define SET_CNT_CTL 0x8U +#define GET_CNT_CTL 0x9U +#define SET_RAND_EN 0xAU +#define GET_RAND_EN 0xBU + +#define SET_POLL_SIZE 0xCU +#define GET_POLL_SIZE 0xDU +#define SET_BUFFER_SIZE 0xEU +#define GET_BUFFER_SIZE 0xFU + +#define RESET_BUFFER 0x10U + +#define GET_LOST 0xEEU +#define DEBUG_BUFFER 0xEFU + +#endif /* IBS_UAPI_H */ + +``` + +`lib/LICENSE`: + +``` +Copyright (c) 2015-2017 Advanced Micro Devices, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. The names of its contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +``` + +`lib/Makefile`: + +``` +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See lib/LICENSE for licensing details. + +include ../make/master.mk + +LIB_DIR_CSOURCES=$(shell find ${LIB_DIR} -name "*.c" -type f) +LIB_DIR_COBJECTS=$(LIB_DIR_CSOURCES:.c=.o) +LIB_DIR_CDEPS=$(LIB_DIR_COBJECTS:.o=.d) + +BUILD_THESE=$(LIB_DIR) + +CFLAGS += -fPIC + +TARGET = libibs +VERSION = 1 + +all: $(TARGET).so.$(VERSION) + +$(TARGET).so.$(VERSION): $(LIB_DIR_COBJECTS) + $(CC) -shared $(CFLAGS) -o $@ $^ + ln -f -s $(TARGET).so.$(VERSION) $(LIB_DIR)/$(TARGET).so + +clean: + rm -f $(TARGET).so.$(VERSION) $(TARGET).so $(LIB_DIR_COBJECTS) $(LIB_DIR_CDEPS) + +-include $(LIB_DIR_CDEPS) + +``` + +`lib/ibs.c`: + +```c +/* + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This file is distributed under the BSD license described in lib/LICENSE + * + * This is a library that implements common functionality for configuring, + * enabling/disabling, and taking samples from the AMD Research IBS driver. + * + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ibs.h" +#include "ibs-uapi.h" + +#define MSEC_PER_SEC 1000 +#define USEC_PER_MSEC 1000 + + +static unsigned char ibs_debug_on = DEFAULT_IBS_DEBUG; +static unsigned char ibs_op = DEFAULT_IBS_OP; +static unsigned char ibs_fetch = DEFAULT_IBS_FETCH; +static unsigned char ibs_aggressive_read = DEFAULT_IBS_AGGRESSIVE_READ; +static unsigned char ibs_read_on_timeout = DEFAULT_IBS_READ_ON_TIMEOUT; +static unsigned long ibs_poll_timeout = DEFAULT_IBS_POLL_TIMEOUT; +static unsigned long ibs_poll_num_samples = DEFAULT_IBS_POLL_NUM_SAMPLES; +static unsigned long ibs_max_cnt = DEFAULT_IBS_MAX_CNT; + +static char * ibs_cpu_list = NULL; + +static unsigned long ibs_daemon_max_samples = DEFAULT_IBS_DAEMON_MAX_SAMPLES; +static word_t ibs_daemon_cpu_list = DEFAULT_IBS_DAEMON_CPU_LIST; +static char * ibs_daemon_op_file = DEFAULT_IBS_DAEMON_OP_FILE; +static char * ibs_daemon_fetch_file = DEFAULT_IBS_DAEMON_FETCH_FILE; + + + static void +DEFAULT_IBS_DAEMON_OP_WRITE(FILE * fp, + ibs_op_t * op) +{ + fprintf(fp, "Got IBS OP sample on cpu %d\n", op->cpu); +} + + static void +DEFAULT_IBS_DAEMON_FETCH_WRITE(FILE * fp, + ibs_fetch_t * fetch) +{ + fprintf(fp, "Got IBS FETCH sample on cpu %d\n", fetch->cpu); +} + +/* Write format for ops/fetches */ +static void (*ibs_daemon_op_write) (FILE * fp, ibs_op_t *) = DEFAULT_IBS_DAEMON_OP_WRITE; +static void (*ibs_daemon_fetch_write)(FILE * fp, ibs_fetch_t *) = DEFAULT_IBS_DAEMON_FETCH_WRITE; + + +/* Per CPU IBS stuff */ +typedef struct ibs_cpu { + int op_enabled; + int op_fd; + int fetch_enabled; + int fetch_fd; + int cpu; +} ibs_cpu_t; + +static int ibs_initialized = 0; +static pid_t ibs_daemon = 0; +static int ibs_max_op_fd = -1; +static int ibs_max_fetch_fd = -1; +static int num_cpus = 0; +ibs_cpu_t * ibs_cpus = NULL; + + +extern int errno; + + + +static int +start_ibs_daemon(void); + + + +#define ibs_debug(fmt, ...) \ + if (ibs_debug_on) \ +printf("IBS_DEBUG [%s:%d:%s]: "fmt"\n", __FILE__, __LINE__, __func__, __VA_ARGS__); + +#define ibs_error(fmt, ...) \ + fprintf(stderr, "IBS_ERROR [%s:%d:%s]: "fmt"\n", __FILE__, __LINE__, __func__, __VA_ARGS__); + +#define ibs_error_no(fmt, ...) \ + fprintf(stderr, "IBS_ERROR [%s:%d:%s]: "fmt": %s\n", __FILE__, __LINE__, __func__, __VA_ARGS__, strerror(errno)); + + + + static void +ibs_op_dev_path(int cpu, + char * path, + int path_len) +{ + snprintf(path, path_len, "/dev/cpu/%d/ibs/op", cpu); +} + + + static void +ibs_fetch_dev_path(int cpu, + char * path, + int path_len) +{ + snprintf(path, path_len, "/dev/cpu/%d/ibs/fetch", cpu); +} + + + + static int +ibs_apply_ioctl_on_cpu(int cmd, + unsigned long arg, + int cpu) +{ + ibs_cpu_t * ibs_cpu = &(ibs_cpus[cpu]); + int status = 0; + + if (ibs_cpu->op_fd > 0) { + status = ioctl(ibs_cpu->op_fd, cmd, arg); + if (status < 0) { + ibs_error_no("ioctl %d on cpu %d op failed", cmd, cpu); + return -1; + } + } + else + { + ibs_debug("Skipping Op IOCTL for CPU %d", cpu); + } + + if (ibs_cpu->fetch_fd > 0) { + status = ioctl(ibs_cpu->fetch_fd, cmd, arg); + if (status < 0) { + ibs_error_no("ioctl %d on cpu %d fetch failed", cmd, cpu); + return -1; + } + } + else + { + ibs_debug("Skipping Fetch IOCTL for CPU %d", cpu); + } + + return 0; +} + +/* Options that require us to take some action, usually on the fds */ + static int +ibs_apply_options_on_cpu(int cpu) +{ + int status; + + ibs_debug("Setting IBS max count on CPU %d to %lu", cpu, ibs_max_cnt); + status = ibs_apply_ioctl_on_cpu( + SET_MAX_CNT, + ibs_max_cnt, + cpu); + if (status < 0) { + ibs_error("Could not apply ibs option SET_MAX_CNT on cpu %d", cpu); + return status; + } + + ibs_debug("Setting IBS poll size count on CPU %d to %lu", cpu, ibs_poll_num_samples); + status = ibs_apply_ioctl_on_cpu( + SET_POLL_SIZE, + ibs_poll_num_samples, + cpu); + if (status < 0) { + ibs_error("Could not apply ibs option SET_POLL_SIZE on cpu %d", cpu); + return status; + } + + return 0; +} + + int +ibs_set_option(ibs_option_t opt, + ibs_val_t val) +{ + union + { + void (*op_write_fp)(FILE *, ibs_op_t *); + void (*fetch_write_fp)(FILE *, ibs_fetch_t *); + void *ptr; + } func_ptr_cast; + switch (opt) { + case IBS_DEBUG: + ibs_debug_on = (unsigned char)(unsigned long)val; + ibs_debug("IBS Debugging enabled%s",""); + break; + + case IBS_OP: + ibs_op = (unsigned char)(unsigned long)val; + ibs_debug("Setting IBS OP mode to %u", ibs_op); + break; + + case IBS_FETCH: + ibs_fetch = (unsigned char)(unsigned long)val; + ibs_debug("Setting IBS FETCH mode to %u", ibs_fetch); + break; + + case IBS_AGGRESSIVE_READ: + ibs_aggressive_read = (unsigned char)(unsigned long)val; + ibs_debug("Setting IBS AGGRESSIVE_READ to %u", ibs_aggressive_read); + break; + + case IBS_READ_ON_TIMEOUT: + ibs_read_on_timeout = (unsigned char)(unsigned long)val; + ibs_debug("Setting IBS READ_ON_TIMEOUT to %u", ibs_read_on_timeout); + break; + + case IBS_POLL_TIMEOUT: + ibs_poll_timeout = (unsigned long)val; + ibs_debug("Setting IBS POLL_TIMEOUT to %lu ms", (unsigned long)val); + break; + + case IBS_POLL_NUM_SAMPLES: + ibs_poll_num_samples = (unsigned long)val; + ibs_debug("Setting IBS POLL_NUM_SAMPLES to %lu samples", (unsigned long)val); + break; + + case IBS_MAX_CNT: + ibs_max_cnt = (unsigned long)val; + ibs_debug("Setting IBS_MAX_CNT to %lu", ibs_max_cnt); + break; + + case IBS_CPU_LIST: + ibs_debug("Setting the IBS_CPU_LIST for %d cores...", num_cpus); + for (int i = 0; i < num_cpus; i++) + { + ibs_debug(" Core %d: %d -> %d\n", i, (int)ibs_cpu_list[i], (int)((char *)val)[i]); + ibs_cpu_list[i] = ((char *)val)[i]; + } + break; + + case IBS_DAEMON_MAX_SAMPLES: + ibs_daemon_max_samples = (unsigned long)val; + ibs_debug("Setting IBS_DAEMON_MAX_SAMPLES to %lu", ibs_daemon_max_samples); + break; + + case IBS_DAEMON_CPU_LIST: + ibs_daemon_cpu_list = (word_t)val; + ibs_debug("Setting IBS_DAEMON_CPU_LIST to 0x%lx", ibs_daemon_cpu_list); + break; + + case IBS_DAEMON_OP_FILE: + ibs_daemon_op_file = (char *)val; + ibs_debug("Setting IBS_DAEMON_OP_FILE to %s", ibs_daemon_op_file); + break; + + case IBS_DAEMON_FETCH_FILE: + ibs_daemon_fetch_file = (char *)val; + ibs_debug("Setting IBS_DAEMON_FETCH_FILE to %s", ibs_daemon_fetch_file); + break; + + case IBS_DAEMON_OP_WRITE: + func_ptr_cast.ptr = val; + ibs_daemon_op_write = func_ptr_cast.op_write_fp; + ibs_debug("Set IBS_DAEMON_OP_WRITE %s", ""); + break; + + case IBS_DAEMON_FETCH_WRITE: + func_ptr_cast.ptr = val; + ibs_daemon_fetch_write = func_ptr_cast.fetch_write_fp; + ibs_debug("Set IBS_DAEMON_FETCH_WRITE %s", ""); + break; + + default: + ibs_error("Unrecognized IBS option: %d", opt); + return -1; + } + + return 0; +} + + int +ibs_enable_cpu(int cpu) +{ + int status; + ibs_cpu_t * ibs_cpu; + + if (!ibs_cpu_list[cpu]) + { + ibs_error("Trying to enable IBS on non-initialized CPU %d", cpu); + return -1; + } + + ibs_cpu = &(ibs_cpus[cpu]); + + if (ibs_cpu->op_fd > 0) { + status = ioctl(ibs_cpu->op_fd, IBS_ENABLE); + if (status < 0) { + ibs_error_no("Cannot enable IBS OP on cpu %d", cpu); + goto err; + } + + ibs_debug("Enabled IBS OP on CPU %d", cpu); + ibs_cpu->op_enabled = 1; + } + + if (ibs_cpu->fetch_fd > 0) { + status = ioctl(ibs_cpu->fetch_fd, IBS_ENABLE); + if (status < 0) { + ibs_error_no("Cannot enable IBS FETCJ on cpu %d", cpu); + goto err; + } + + ibs_debug("Enabled IBS FETCH on CPU %d", cpu); + ibs_cpu->fetch_enabled = 1; + } + + return 0; + +err: + ibs_disable_cpu(cpu); + return status; +} + + int +ibs_enable_all(void) +{ + int cpu, status = 0; + + for (cpu = 0; cpu < num_cpus; cpu++) { + ibs_debug("Checking if IBS is initialized for CPU %d: %d", cpu, ibs_cpu_list[cpu]); + if (ibs_cpu_list[cpu]) { + status = ibs_enable_cpu(cpu); + if (status < 0) { + ibs_error("Cannot enable IBS on cpu %d", cpu); + goto enable_err; + } + } + } + + return 0; + +enable_err: + while (cpu >= 0) { + if (ibs_cpu_list[cpu]) { + ibs_disable_cpu(cpu); + } + + cpu--; + } + + return status; +} + + void +ibs_disable_cpu(int cpu) +{ + int status; + ibs_cpu_t * ibs_cpu; + + if (!ibs_cpu_list[cpu]) { + ibs_error("Trying to disble IBS on non-initialized CPU %d", cpu); + return; + } + + ibs_cpu = &(ibs_cpus[cpu]); + + if (ibs_cpu->op_enabled) { + status = ioctl(ibs_cpu->op_fd, IBS_DISABLE); + if (status < 0) { + ibs_error_no("Cannot disable IBS OP on cpu %d", cpu); + } + + ibs_cpu->op_enabled = 0; + ibs_debug("Disabled IBS OP on CPU %d", cpu); + } + + if (ibs_cpu->fetch_enabled > 0) { + status = ioctl(ibs_cpu->fetch_fd, IBS_DISABLE); + if (status < 0) { + ibs_error_no("Cannot disable IBS FETCH on cpu %d", cpu); + } + + ibs_cpu->fetch_enabled = 0; + ibs_debug("Disabled IBS FETCH on CPU %d", cpu); + } +} + + void +ibs_disable_all(void) +{ + int cpu; + + for (cpu = 0; cpu < num_cpus; cpu++) { + if (ibs_cpu_list[cpu]) { + ibs_disable_cpu(cpu); + } + } +} + + static int +do_ibs_get_sample(ibs_sample_type_t type, + int fd, + ibs_sample_t * samples, + int sample_off, + unsigned int max_samples) +{ + unsigned int samples_available; + int bytes_wanted, bytes_read; + + /* How many samples are available? */ + samples_available = ioctl(fd, FIONREAD); + switch (samples_available) { + case -1: + ibs_error_no("Could not read number of samples in fd %d", fd); + return -1; + + case 0: + ibs_error("No samples avaialable in fd %d", fd); + return -1; + + default: + if ((ibs_aggressive_read == 0) && + (samples_available < ibs_poll_num_samples)) + { + ibs_error("%u samples available in fd %d, but select said at least %lu were!!", + samples_available, + fd, + ibs_poll_num_samples); + return -1; + } + break; + } + + if (samples_available > max_samples) + samples_available = max_samples; + + ibs_op_t *temp_op_buffer = NULL; + ibs_fetch_t *temp_fetch_buffer = NULL; + if (type == IBS_OP_SAMPLE) + { + bytes_wanted = samples_available * sizeof(ibs_op_t); + temp_op_buffer = malloc(bytes_wanted); + bytes_read = read(fd, (void*)temp_op_buffer, bytes_wanted); + } + else + { + bytes_wanted = samples_available * sizeof(ibs_fetch_t); + temp_fetch_buffer = malloc(bytes_wanted); + bytes_read = read(fd, (void*)temp_fetch_buffer, bytes_wanted); + } + + switch (bytes_read) { + case -1: + ibs_error_no("Could not read samples from fd %d", fd); + if (temp_op_buffer) + free(temp_op_buffer); + if (temp_fetch_buffer) + free(temp_fetch_buffer); + return -1; + + case 0: + ibs_error("Read 0 bytes from fd %d, which should be impossible with O_NONBLOCK", fd); + if (temp_op_buffer) + free(temp_op_buffer); + if (temp_fetch_buffer) + free(temp_fetch_buffer); + return -1; + + default: + if (bytes_read < bytes_wanted) { + ibs_error("Read %d bytes out %d avaialable. This should not be possible", + bytes_read, bytes_wanted); + if (temp_op_buffer) + free(temp_op_buffer); + if (temp_fetch_buffer) + free(temp_fetch_buffer); + return -1; + } + break; + } + + for (unsigned int i = 0; i < samples_available; i++) + { + if (type == IBS_OP_SAMPLE) + samples[sample_off + i].ibs_sample.op = temp_op_buffer[i]; + else + samples[sample_off + i].ibs_sample.fetch = temp_fetch_buffer[i]; + } + + if (temp_op_buffer != NULL) + free(temp_op_buffer); + if (temp_fetch_buffer != NULL) + free(temp_fetch_buffer); + + return samples_available; +} + + static int +do_ibs_get_all_samples(int max_samples, + int sample_flags, + ibs_sample_t * samples, + ibs_sample_type_t * sample_types, + fd_set * fd_set, + char * cpu_list) +{ + int total_new_samples, sample_off, new_samples, cpu; + + sample_off = 0; + total_new_samples = 0; + + for (cpu = 0; cpu < num_cpus; cpu++) { + ibs_cpu_t * ibs_cpu = &(ibs_cpus[cpu]); + + if (!cpu_list[cpu]) + continue; + + /* aggressive_read -> don't even check if the fd has is set. The idea is that + * at least one cpu has met the threshold, so it might make sense to read all + * cpus now. + */ + if ((sample_flags & IBS_OP_SAMPLE) & + (ibs_cpu->op_fd > 0) && + ( + (ibs_aggressive_read || + (FD_ISSET(ibs_cpu->op_fd, fd_set))) + ) + ) + { + new_samples = do_ibs_get_sample( + IBS_OP_SAMPLE, + ibs_cpu->op_fd, + samples, + sample_off, + max_samples - total_new_samples); + + if (new_samples < 0) { + ibs_error("Could not get OP sample from cpu %d", cpu); + } else { + /* Remember that these are ops */ + { + int opt; + for (opt = 0; opt < new_samples; opt++) { + ibs_sample_type_t * type = &(sample_types[sample_off + opt]); + *type = IBS_OP_SAMPLE; + } + } + + total_new_samples += new_samples; + sample_off += new_samples; + + if (total_new_samples == max_samples) + break; + } + } + + if ((sample_flags & IBS_FETCH_SAMPLE) && + (ibs_cpu->fetch_fd > 0) && + ( + (ibs_aggressive_read || + (FD_ISSET(ibs_cpu->fetch_fd, fd_set))) + ) + ) + { + new_samples = do_ibs_get_sample( + IBS_FETCH_SAMPLE, + ibs_cpu->fetch_fd, + samples, + sample_off, + max_samples - total_new_samples); + + if (new_samples < 0) { + ibs_error("Could not get FETCH sample from cpu %d", cpu); + } else { + /* Remember that these are fetches */ + { + int opt; + for (opt = 0; opt < new_samples; opt++) { + ibs_sample_type_t * type = &(sample_types[sample_off + opt]); + *type = IBS_FETCH_SAMPLE; + } + } + + total_new_samples += new_samples; + sample_off += new_samples; + + if (total_new_samples == max_samples) + break; + } + } + } + + return total_new_samples; +} + + static int +do_ibs_sample(int max_samples, + int sample_flags, + ibs_sample_t * samples, + ibs_sample_type_t * sample_types, + char * cpu_list) +{ + int max_fd, cpu, status; + struct timeval timeout; + fd_set rfds; + + if (max_samples <= 0) { + ibs_error("max_samples must be > 0. Sent %d instead.", max_samples); + return -1; + } + + if (!(sample_flags & IBS_OP_SAMPLE) && + !(sample_flags & IBS_FETCH_SAMPLE)) + { + ibs_error("Must supply IBS_OP_SAMPLE and/or IBS_FETCH_SAMPLE. Sent %d instead.", + sample_flags); + return -1; + } + + FD_ZERO(&rfds); + for (cpu = 0; cpu < num_cpus; cpu++) { + if (cpu_list[cpu]) { + ibs_cpu_t * ibs_cpu = &(ibs_cpus[cpu]); + if ((sample_flags & IBS_OP_SAMPLE) && ibs_cpu->op_enabled) { + FD_SET(ibs_cpu->op_fd, &rfds); + } + + if ((sample_flags & IBS_FETCH_SAMPLE) && ibs_cpu->fetch_enabled) { + FD_SET(ibs_cpu->fetch_fd, &rfds); + } + } + } + + /* Get max select fd */ + max_fd = (ibs_max_op_fd > ibs_max_fetch_fd) ? ibs_max_op_fd : ibs_max_fetch_fd; + + if (ibs_poll_timeout > 0) { + timeout.tv_sec = (ibs_poll_timeout / MSEC_PER_SEC); + timeout.tv_usec = (ibs_poll_timeout % MSEC_PER_SEC) * USEC_PER_MSEC; + status = select(max_fd + 1, &rfds, NULL, NULL, &timeout); + } else { + status = select(max_fd + 1, &rfds, NULL, NULL, NULL); + } + + switch (status) { + case -1: + if (errno != EINTR) + { + ibs_error_no("Select failed.%s", ""); + return -1; + } + /* We may still want to read whatever's there */ + if (ibs_read_on_timeout) + break; + + /* Else we bail */ + return 0; + + case 0: + ibs_debug("select timed out after %lu ms of no more than %lu samples", + ibs_poll_timeout, + ibs_poll_num_samples); + + /* We may still want to read whatever's there */ + if (ibs_read_on_timeout) + break; + + /* Else we bail */ + return 0; + + default: + break; + } + + return do_ibs_get_all_samples( + max_samples, + sample_flags, + samples, + sample_types, + &rfds, + cpu_list); +} + + +/* Get some IBS samples */ + int +ibs_sample(int max_samples, + int sample_flags, + ibs_sample_t * samples, + ibs_sample_type_t * sample_types) +{ + return do_ibs_sample( + max_samples, + sample_flags, + samples, + sample_types, + ibs_cpu_list); +} + +static int is_cpu_online(int cpu_num) +{ + char *online_name; + + // CPU 0 is the bootstrap processor and is always on + if (cpu_num == 0) + return 1; + + int num_bytes = asprintf(&online_name, + "/sys/devices/system/cpu/cpu%d/online", cpu_num); + if (num_bytes <= 0) + { + fprintf(stderr, "asprintf failed at %s:%d\n", __FILE__, __LINE__); + exit(EXIT_FAILURE); + } + FILE *online_fp = fopen(online_name, "r"); + if (online_fp == NULL) + { + fprintf(stderr, "Could not open %s\n", online_name); + exit(EXIT_FAILURE); + } + free(online_name); + + char *online_cpus = NULL; + size_t len = 0; + ssize_t line_len = getline(&online_cpus, &len, online_fp); + if (line_len == -1 || len == 0) + { + fprintf(stderr, "Failed to read the number of online CPUs\n"); + exit(EXIT_FAILURE); + } + fclose(online_fp); + + if (online_cpus[0] == '1') + return 1; + else + return 0; +} + + static int +do_ibs_initialize(ibs_option_list_t * options, + int num_options) +{ + char ibs_path[256]; + int status = 0, fd = 0, opt = 0, cpu = 0; + + num_cpus = get_nprocs_conf(); + ibs_cpu_list = calloc(num_cpus, sizeof(char)); + + /* Save options */ + for (opt = 0; opt < num_options; opt++) { + ibs_option_list_t * o = &(options[opt]); + ibs_set_option(o->opt, o->val); + } + + int online_cpus = get_nprocs(); + ibs_debug("%d total cpus - %d cpus online", num_cpus, online_cpus); + + /* Allocate memory for cpu structs */ + ibs_cpus = malloc(sizeof(ibs_cpu_t) * num_cpus); + if (ibs_cpus == NULL) { + ibs_error_no("malloc failed.%s", ""); + return -1; + } + + memset(ibs_cpus, 0, sizeof(ibs_cpu_t) * num_cpus); + for (cpu = 0; cpu < num_cpus; cpu++) { + ibs_cpu_t * ibs_cpu = &(ibs_cpus[cpu]); + ibs_cpu->cpu = cpu; + } + + ibs_max_op_fd = -1; + ibs_max_fetch_fd = -1; + + /* Open IBS files and store fds */ + for (cpu = 0; cpu < num_cpus; cpu++) { + ibs_cpu_t * ibs_cpu = &(ibs_cpus[cpu]); + int online = is_cpu_online(cpu); + if (!online) + continue; + + if (ibs_op) { + ibs_debug("Opening IBS-Op device on CPU %d", cpu); + ibs_op_dev_path(cpu, ibs_path, 256); + + fd = open(ibs_path, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + ibs_error_no("Failed to open %s", ibs_path); + goto err; + } + + ibs_cpu->op_fd = fd; + if (fd > ibs_max_op_fd) { + ibs_max_op_fd = fd; + } + } + + if (ibs_fetch) { + ibs_debug("Opening IBS-Fetch device on CPU %d", cpu); + ibs_fetch_dev_path(cpu, ibs_path, 32); + + fd = open(ibs_path, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + ibs_error_no("Failed to open %s", ibs_path); + goto err; + } + + ibs_cpu->fetch_fd = fd; + if (fd > ibs_max_fetch_fd) { + ibs_max_fetch_fd = fd; + } + } + + /* Apply options on the cpus before enabling IBS */ + status = ibs_apply_options_on_cpu(cpu); + if (status != 0) { + ibs_error("Could not apply options on cpu %d", cpu); + goto err; + } + } + + ibs_debug("IBS Initialized.%s", ""); + + ibs_initialized = 1; + return 0; + +err: + while (cpu >= 0) { + ibs_cpu_t * ibs_cpu = &(ibs_cpus[cpu]); + if (ibs_cpu->op_fd > 0) { + close(ibs_cpu->op_fd); + ibs_cpu->op_fd = 0; + } + + if (ibs_cpu->fetch_fd > 0) { + close(ibs_cpu->fetch_fd); + ibs_cpu->fetch_fd = 0; + } + + cpu--; + } + + free(ibs_cpus); + + ibs_max_op_fd = -1; + ibs_max_fetch_fd = -1; + + return fd; +} + + +/* Basic initialization */ + int +ibs_initialize(ibs_option_list_t * options, + int num_options, + int daemonize) +{ + int ret; + + if (ibs_initialized) { + ibs_error("IBS already initialized. %s", ""); + errno = EALREADY; + return -1; + } + + ret = do_ibs_initialize(options, num_options); + if (ret != 0) { + ibs_error("Failed to initialize IBS. Ret: %d", ret); + return ret; + } + + if (daemonize) { + pid_t pid = fork(); + + switch (pid) { + case -1: + ibs_error_no("Failed to fork daemon. %s", ""); + return -1; + + case 0: + break; + + default: + ibs_daemon = pid; + return 0; + } + + start_ibs_daemon(); + exit(ret); + } + + /* Success */ + return 0; +} + + + void +ibs_finalize(void) +{ + if (ibs_daemon) { + /* Send a signal to kill the daemon */ + kill(ibs_daemon, SIGUSR1); + ibs_daemon = 0; + return; + } + + if (!ibs_initialized) { + ibs_error("IBS not initialized. %s", ""); + return; + } + + /* Disable IBS on all cpus */ + ibs_disable_all(); + + /* Free resources */ + free(ibs_cpus); + + ibs_max_op_fd = -1; + ibs_max_fetch_fd = -1; + ibs_initialized = 0; +} + + + +static int die = 0; + + static void +sig_handler(int sig) +{ + if (sig != SIGUSR1 && sig != SIGINT) { + ibs_error("Child received a weird signal (%d)", sig); + return; + } + + die = 1; +} + + +/* Setup a simple sample loop */ + static int +start_ibs_daemon(void) +{ + int status; + ibs_sample_t * samples = NULL; + ibs_sample_type_t * sample_types = NULL; + FILE * op_fp = NULL, * fetch_fp = NULL; + unsigned long num_ops = 0, num_fetches = 0, num_samples = 0; + + if (!ibs_fetch && !ibs_op) + return 0; + + /* Allocate some memory for the samples */ + samples = malloc(sizeof(ibs_sample_t) * ibs_daemon_max_samples); + if (samples == NULL) { + ibs_error_no("Cannot malloc %lu samples", ibs_daemon_max_samples); + return -1; + } + + sample_types = malloc(sizeof(ibs_sample_type_t) * ibs_daemon_max_samples); + if (sample_types == NULL) { + ibs_error_no("Cannot malloc %lu sample types", ibs_daemon_max_samples); + free(samples); + return -1; + } + + /* Open the op file */ + if (ibs_op) + { + op_fp = fopen(ibs_daemon_op_file, "w"); + if (op_fp == NULL) { + ibs_error_no("Cannot open output file %s", ibs_daemon_op_file); + free(samples); + free(sample_types); + return -1; + } + } + + /* Open the fetch file */ + if (ibs_fetch) + { + fetch_fp = fopen(ibs_daemon_fetch_file, "w"); + if (fetch_fp == NULL) { + ibs_error_no("Cannot open output file %s", ibs_daemon_fetch_file); + free(samples); + free(sample_types); + if (op_fp) + fclose(op_fp); + return -1; + } + } + + /* Enable all CPUs */ + status = ibs_enable_all(); + if (status != 0) { + ibs_error("Cannot enable IBS on all CPUs. Status: %d", status); + free(sample_types); + free(samples); + if (op_fp) + fclose(op_fp); + if (fetch_fp) + fclose(fetch_fp); + return status; + } + + /* Register a handler for the parent to kill us with */ + signal(SIGUSR1, sig_handler); + + /* Also catch ctrl-C */ + signal(SIGINT, sig_handler); + + while (die == 0) { + int new_samples, i, sample_flags = 0; + + if (ibs_op) + sample_flags |= IBS_OP_SAMPLE; + + if (ibs_fetch) + sample_flags |= IBS_FETCH_SAMPLE; + + new_samples = ibs_sample( + ibs_daemon_max_samples, + sample_flags, + samples, + sample_types); + + if (new_samples >= 0) + num_samples += new_samples; + + for (i = 0; i < new_samples; i++) { + ibs_sample_t * sample = &(samples[i]); + ibs_sample_type_t type = sample_types[i]; + + if (type == IBS_OP_SAMPLE) { + assert(sample_flags & IBS_OP_SAMPLE); + ibs_op_t * op = &(sample->ibs_sample.op); + ibs_daemon_op_write(op_fp, op); + num_ops++; + } else { + assert(sample_flags & IBS_FETCH_SAMPLE); + ibs_fetch_t * fetch = &(sample->ibs_sample.fetch); + ibs_daemon_fetch_write(fetch_fp, fetch); + num_fetches++; + } + } + } + + if (op_fp) + { + fprintf(op_fp, "IBS OP samples: %lu\n", num_ops); + fprintf(op_fp, "IBS total samples: %lu\n", num_samples); + fclose(op_fp); + } + if (fetch_fp) + { + fprintf(fetch_fp, "IBS FETCH samples: %lu\n", num_fetches); + fprintf(fetch_fp, "IBS total samples: %lu\n", num_samples); + fclose(fetch_fp); + } + + free(samples); + free(sample_types); + + ibs_finalize(); + return 0; +} + +``` + +`lib/ibs.h`: + +```h +/* + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This file is distributed under the BSD license described in lib/LICENSE + * + * This is a library that implements common functionality for configuring, + * enabling/disabling, and taking samples from the AMD Research IBS driver. + * + */ + +#ifndef __IBS_H__ +#define __IBS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "ibs-uapi.h" + + +#define DEFAULT_IBS_DEBUG 0 +#define DEFAULT_IBS_OP 0 +#define DEFAULT_IBS_FETCH 0 +#define DEFAULT_IBS_AGGRESSIVE_READ 0 +#define DEFAULT_IBS_READ_ON_TIMEOUT 1 +#define DEFAULT_IBS_POLL_TIMEOUT 1000 +#define DEFAULT_IBS_POLL_NUM_SAMPLES 4096 +#define DEFAULT_IBS_MAX_CNT 0x3fff +#define DEFAULT_IBS_CPU_LIST (word_t)-1 + +#define DEFAULT_IBS_DAEMON_MAX_SAMPLES 10000 +#define DEFAULT_IBS_DAEMON_OP_FILE "op.ibs" +#define DEFAULT_IBS_DAEMON_FETCH_FILE "fetch.ibs" +#define DEFAULT_IBS_DAEMON_CPU_LIST (word_t)-1 + + + +typedef uint64_t word_t; +#define BITS_PER_WORD sizeof(word_t) +#define WORD_OFFSET(b) ((b) / BITS_PER_WORD) +#define BIT_OFFSET(b) ((b) % BITS_PER_WORD) + +#define SET_BIT(WORD, BIT) \ + ((word_t *)&WORD)[WORD_OFFSET(BIT)] |= (1 << BIT_OFFSET(BIT)) + +#define CLEAR_BIT(WORD, BIT) \ + ((word_t *)&WORD)[WORD_OFFSET(BIT)] &= ~(1 << BIT_OFFSET(BIT)) + +#define IS_BIT_SET(WORD, BIT) \ + !!(((word_t *)&WORD)[WORD_OFFSET(BIT)] & (1 << BIT_OFFSET(BIT))) + +typedef enum { + IBS_DEBUG, + IBS_OP, + IBS_FETCH, + IBS_AGGRESSIVE_READ, + IBS_READ_ON_TIMEOUT, + IBS_POLL_TIMEOUT, + IBS_POLL_NUM_SAMPLES, + IBS_MAX_CNT, + IBS_CPU_LIST, + IBS_DAEMON_MAX_SAMPLES, + IBS_DAEMON_CPU_LIST, + IBS_DAEMON_OP_FILE, + IBS_DAEMON_FETCH_FILE, + IBS_DAEMON_OP_WRITE, + IBS_DAEMON_FETCH_WRITE, +} ibs_option_t; + +typedef void * ibs_val_t; + + +typedef struct ibs_option_list { + ibs_option_t opt; + ibs_val_t val; +} ibs_option_list_t; + + +typedef enum { + IBS_OP_SAMPLE = 0x1, + IBS_FETCH_SAMPLE = 0x2 +} ibs_sample_type_t; + + +typedef struct ibs_sample { + union { + struct ibs_op op; + struct ibs_fetch fetch; + } ibs_sample; +} ibs_sample_t; + + + +/* Initialize IBS with list of options */ +int +ibs_initialize(ibs_option_list_t *, int num_opts, int daemonize); + +void +ibs_finalize(void); + +/* Set an IBS option */ +int +ibs_set_option(ibs_option_t, ibs_val_t); + +/* Enable IBS on a cpu */ +int +ibs_enable_cpu(int cpu); + +/* Disable IBS on a cpu */ +void +ibs_disable_cpu(int cpu); + +/* Enable IBS on all cpus */ +int +ibs_enable_all(void); + +/* Disable IBS on all cpus */ +void +ibs_disable_all(void); + +/* Get some IBS samples */ +int +ibs_sample(int max_samples, + int sample_flags, + struct ibs_sample * samples, + ibs_sample_type_t * sample_types); + +#ifdef __cplusplus +} +#endif + +#endif /* __IBS_H__ */ + +``` + +`make/LICENSE.bsd`: + +```bsd +Copyright (c) 2015-2017 Advanced Micro Devices, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. The names of its contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +``` + +`make/LICENSE.gpl`: + +```gpl +This software is Copyright (c) 2015-2017 Advanced Micro Devices, Inc. + +---------------------------------------- + + NOTE! This copyright does *not* cover user programs that use kernel + services by normal system calls - this is merely considered normal use + of the kernel, and does *not* fall under the heading of "derived work". + Also note that the GPL below is copyrighted by the Free Software + Foundation, but the instance of code that it refers to (the linux + kernel) is copyrighted by me and others who actually wrote it. + + Linus Torvalds + +---------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +``` + +`make/master.mk`: + +```mk +# Copyright (C) 2015-2017 Advanced Micro Devices, Inc. +# +# This file is distributed under the BSD license described in +# make/LICENSE.bsd +# Alternatively, this file may be distributed under the terms of the +# Linux kernel's version of the GPLv2. See make/LICENSE.gpl + +############################################## +# AMD Research IBS Toolkit Make Defines +############################################## + +HERE := $(dir $(lastword $(MAKEFILE_LIST))) + +ifeq ($(CC),cc) +CC=gcc +endif + +INCLUDE_DIR=${HERE}../include +DRIVER_DIR=${HERE}../driver +LIB_DIR=${HERE}../lib +TOOLS_DIR=${HERE}../tools +ANNOTATE_DIR=${TOOLS_DIR}/ibs_run_and_annotate +REPO_DIR:=$(HERE)../ +REPO_SUBDIRS := $(wildcard $(REPO_DIR)/.) + +# Name of all the source files we need for the detector +DRIVER_CSOURCES=$(shell find ${DRIVER_DIR} -name "*.c" -type f) +DRIVER_COBJECTS=$(DRIVER_CSOURCES:.c=.o) +DRIVER_CDEPS=$(DRIVER_COBJECTS:.o=.d) + +INCLUDE_FLAGS=-I$(INCLUDE_DIR) +WERROR_FLAG=-Werror + +ifneq ($(SKIP_PEDANTIC),1) +ifeq ($(CC),gcc) +GCC_GTE_49 := $(shell expr `gcc -dumpversion | cut -f1,2 -d.` \>= 4.9) +ifeq "$(GCC_GTE_49)" "1" +PEDANTIC_FLAGS=-Wpedantic -pedantic-errors +endif +endif +endif + +ifeq ($(CC),gcc) +GCC_GTE_42 := $(shell expr `gcc -dumpversion | cut -f1,2 -d.` \>= 4.2) +ifeq "$(GCC_GTE_42)" "1" +MARCH_FLAGS=-march=native +endif +endif + +CPPCHECK_GTE_159 := $(shell which cppcheck > /dev/null; if test $$? -eq 0; then expr `cppcheck --version | awk '{print $$2}'` \>= 1.59; else echo 0; fi) +ifeq "$(CPPCHECK_GTE_159)" "1" +CPPCHECK_WARN_FLAG=warning, +CPPCHECK_STD_FLAGS=--std=c11 --std=c++11 +endif + +ifneq ($(SKIP_WEXTRA),1) +WEXTRA_FLAGS=-Wextra -Wpacked +endif + +WARN_FLAGS=-Wall $(WEXTRA_FLAGS) $(PEDANTIC_FLAGS) -Wundef +CONLY_WARN=-Wold-style-definition + +# use the following line for normal operation, or use the second line following this one for debugging/profiling +ifndef DEBUG +C_AND_CXX_FLAGS=-g3 -ggdb $(WERROR_FLAG) $(WARN_FLAGS) $(INCLUDE_FLAGS) -O3 $(MARCH_FLAGS) -DNDEBUG +else +C_AND_CXX_FLAGS=-g3 -ggdb -fno-omit-frame-pointer $(WARN_FLAGS) $(INCLUDE_FLAGS) -O0 -DDEBUG +endif + +CFLAGS=$(C_AND_CXX_FLAGS) -std=gnu99 $(CONLY_WARN) +CXXFLAGS=$(C_AND_CXX_FLAGS) -std=c++99 -I$(HERE).. -fno-strict-aliasing -Wformat -Werror=format-security -fwrapv + +default: all + +check: + cppcheck --force --enable=$(CPPCHECK_WARN_FLAG)style,performance,portability,missingInclude --error-exitcode=-1 -D __refdata=" " $(CPPCHECK_STD_FLAGS) $(BUILD_THESE) -q -j `nproc` + $(if $(shell which pylint 2>/dev/null), \ + pylint -E $(ANNOTATE_DIR)/ibs_run_and_annotate, \ + @echo "pylint does not exist, skipping.") + +%.o: %.c + $(CC) $(CFLAGS) -c -MMD -o $@ $< + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -MMD -o $@ $< + +%.d: ; +.PRECIOUS: %.d + +``` + +`prereqs/LICENSE`: + +``` +Copyright (c) 2017 Advanced Micro Devices, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. The names of its contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +``` + +`prereqs/centos_7_install.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See prereqs/LICENSE for licensing details. + +# Likely need git to work with this repo. +sudo yum install -y git + +# Install the build environments that we need for the IBS driver +sudo yum install -y clang +sudo yum install -y clang-analyzer +sudo yum groupinstall -y "Development Tools" +sudo yum install -y wget +wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-9.noarch.rpm +sudo rpm -ivh epel-release-7-9.noarch.rpm +rm -f epel-release-7-9.noarch.rpm +sudo yum install -y "kernel-devel-uname-r == $(uname -r)" +sudo yum install -y cppcheck +sudo yum install -y pylint +sudo yum install -y python-pip +sudp pip install joblib +sudo pip install argparse + +``` + +`prereqs/opensuse_leap_42.2_install.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See prereqs/LICENSE for licensing details. + +# Likely need git to work with this repo. +sudo zypper install -y git + +# Install the build environments that we need for the IBS driver +sudo zypper install -y -t pattern devel_basis devel_C_C++ devel_kernel +sudo zypper instal -y clang +sudo zypper install -y cppcheck +sudo zypper install -y pylint +sudo zypper install -y python-pip python-setuptools +sudo pip install joblib +sudo pip install argparse + +``` + +`prereqs/ubuntu_14.04_install.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See prereqs/LICENSE for licensing details. + +# Likely need git to work with this repo. +sudo apt-get install -y git + +# Install the build environments that we need for the IBS driver +sudo apt-get install -y build-essential clang cppcheck + +# Install Python utilities +sudo apt-get install -y pylint python-pip python-argparse +sudo pip install joblib + +``` + +`prereqs/ubuntu_16.04_install.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2017-2018 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See prereqs/LICENSE for licensing details. + +# Likely need git to work with this repo. +sudo apt-get install -y git + +# Install the build environments that we need for the IBS driver +sudo apt-get install -y build-essential clang cppcheck + +# Install Python utilities +sudo apt-get install -y pylint python-pip python-argparse +sudo pip install joblib parallel + +``` + +`prereqs/ubuntu_17.10_install.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2017-2018 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See prereqs/LICENSE for licensing details. + +# Likely need git to work with this repo. +sudo apt-get install -y git + +# Install the build environments that we need for the IBS driver +sudo apt-get install -y build-essential clang cppcheck + +# Install Python utilities +sudo apt-get install -y pylint python-pip python-argparse +sudo pip install joblib parallel + +``` + +`prereqs/ubuntu_18.04_install.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2017-2018 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See prereqs/LICENSE for licensing details. + +# Likely need git to work with this repo. +sudo apt-get install -y git + +# Install the build environments that we need for the IBS driver +sudo apt-get install -y build-essential clang clang-tools cppcheck + +# Install Python utilities +sudo apt-get install -y pylint python-pip python-argparse python-parallel python-joblib + +``` + +`tools/LICENSE`: + +``` +Copyright (c) 2015-2018 Advanced Micro Devices, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. The names of its contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +``` + +`tools/Makefile`: + +``` +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See lib/LICENSE for licensing details. + +include ../make/master.mk + +BUILD_THESE := $(wildcard */.) + +TARGETS := all clean + +$(TARGETS): $(BUILD_THESE) + +$(BUILD_THESE): + $(MAKE) -C $@ $(MAKECMDGOALS) + +.PHONY: $(TARGETS) $(BUILD_THESE) + +``` + +`tools/common.mk`: + +```mk +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See tools/LICENSE for licensing details. + +COMMON_MK_DIR:= $(dir $(lastword $(MAKEFILE_LIST))) + +include $(COMMON_MK_DIR)../make/master.mk + +THIS_TOOL_DIR_CSOURCES=$(shell find ${THIS_TOOL_DIR} -name "*.c" -type f) +THIS_TOOL_DIR_COBJECTS=$(THIS_TOOL_DIR_CSOURCES:.c=.o) +THIS_TOOL_DIR_CDEPS=$(THIS_TOOL_DIR_COBJECTS:.o=.d) + +BUILD_THESE=$(THIS_TOOL_DIR) + +CFLAGS+=$(TOOL_CFLAGS) +LDFLAGS+=$(TOOL_LDFLAGS) + +.PHONY: all +all: $(THIS_TOOL_DIR)/$(THIS_TOOL_NAME) + +$(THIS_TOOL_DIR)/$(THIS_TOOL_NAME): $(THIS_TOOL_DIR_COBJECTS) + $(CC) -o $@ $^ $(LDFLAGS) + +clean: + rm -f $(THIS_TOOL_DIR)$(THIS_TOOL_NAME) $(THIS_TOOL_DIR_COBJECTS) $(THIS_TOOL_DIR_CDEPS) $(THIS_TOOL_DIR)*.ibs $(THIS_TOOL_DIR)*.out + +-include $(THIS_TOOL_DIR_CDEPS) + +``` + +`tools/ibs_daemon/Makefile`: + +``` +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See tools/LICENSE for licensing details. + +THIS_TOOL_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +THIS_TOOL_NAME := ibs_daemon +TOOL_CFLAGS+=-I $(LIB_DIR) +TOOL_LDFLAGS+=-L $(LIB_DIR) -libs + +include $(THIS_TOOL_DIR)../common.mk + +``` + +`tools/ibs_daemon/ibs_daemon.c`: + +```c +/* + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This application demonstrates how to use the LibIBS "daemon" mode to + * automatically gather samples while other things happen within your + * applications. This prevents the need for users to manually launch an + * IBS monitoring thread, etc. whenever they want to gather IBS samples + * from within their application. + * + * This file is distributed under the BSD license described in tools/LICENSE + */ + + /* This application shows how to use libIBS to write your own custom program + monitoring tool. By setting up a daemon in the background, it's possible + to run a custom function every time a series of IBS samples are read in. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ibs.h" + +/* Emperically, this max count gives us something like 1500 samples + * per second on each CPU */ +#define IBS_WATCH_MAX_CNT 0x2ffff +#define IBS_WATCH_SELECT_TIMEOUT 1000 +#define IBS_WATCH_SELECT_SAMPLES 512 + +#define NR_CPUS sysconf(_SC_NPROCESSORS_ONLN) +#define IBS_WATCH_MAX_SAMPLES ((int)(IBS_WATCH_SELECT_SAMPLES * (1.1)) * NR_CPUS) + +static int header_written = 0; +static char *global_op_file = NULL; +static char *global_work_dir = NULL; + +static void write_header(FILE * fp) +{ + int off = 0; + char buf[1000] = ""; + + { + char hostname[32]; + gethostname(hostname, 32); + fprintf(fp, "%s\n", hostname); + } + + off = sprintf(buf, "tsc,"); + off += sprintf(buf + off, "cpu,"); + off += sprintf(buf + off, "pid,"); + off += sprintf(buf + off, "tid,"); + off += sprintf(buf + off, "kern_mode,"); + off += sprintf(buf + off, "data,"); + off += sprintf(buf + off, "data2,"); + sprintf(buf + off, "data3"); + + fprintf(fp, "%s\n", buf); +} + +static void write_op_misc(ibs_op_t * op, char *buf, int *off) +{ + *off = sprintf(buf, "%" PRIu64, op->tsc); + *off += sprintf(buf + *off, ",%d", op->cpu); + *off += sprintf(buf + *off, ",%d", op->pid); + *off += sprintf(buf + *off, ",%d", op->tid); + *off += sprintf(buf + *off, ",%d", op->kern_mode); +} + +static void write_op_sample(FILE *fp, ibs_op_t *op) +{ + char buf[1000] = ""; + int off = 0; + + // Check for header here, because the IBS Daemon actually opens the + // output file. As such, we can't write it until the daemon calls + // the op sample function. + if (header_written == 0) { + write_header(fp); + header_written = 1; + } + + write_op_misc(op, buf, &off); + off += sprintf(buf + off, ",0x%" PRIx64, op->op_data.val); + off += sprintf(buf + off, ",0x%" PRIx64, op->op_data2.val); + off += sprintf(buf + off, ",0x%" PRIx64, op->op_data3.val); + + fprintf(fp, "%s\n", buf); +} + +// Launch a child process and wait around until it completes. +static void launch_child_work(cpu_set_t *procs, char *argv[]) +{ + pid_t cpid = fork(); + if (cpid == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } + if (cpid == 0) { /* Child process */ + if (global_work_dir != NULL) + { + int err_chk = chdir(global_work_dir); + if (err_chk != 0) + { + fprintf(stderr, "Unable to change working direcotry to: %s\n", + global_work_dir); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + } + + sched_setaffinity(0, sizeof(cpu_set_t), procs); + if (execvp(argv[0], &argv[0]) == -1) { + fprintf(stderr, "Unable to execution application: %s\n", argv[0]); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); + } + waitpid(cpid, NULL, 0); +} + +static void parse_args(int argc, char *argv[]) +{ + static struct option longopts[] = + { + {"op_file", required_argument, NULL, 'o'}, + {"working_dir", required_argument, NULL, 'w'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + char c; + while ((c = getopt_long(argc, argv, "+ho:w:", longopts, NULL)) != -1) + { + switch (c) { + case 'h': + fprintf(stderr, "This program executes another program and\n"); + fprintf(stderr, "collects IBS samples during its execution.\n"); + fprintf(stderr, "Usage: ./ibs_monitor [-o op_output] [-w working_directory] program_to_run [...]\n"); + fprintf(stderr, "--working_dir (or -w):\n"); + fprintf(stderr, " Sets the working direcotry for launching the program to monitor.\n"); + fprintf(stderr, "--op_file (or -o):\n"); + fprintf(stderr, " File to which to save IBS op samples\n"); + fprintf(stderr, "If you skip setting the file, IBS sampling will be disabled.\n\n"); + exit(EXIT_SUCCESS); + case 'o': + global_op_file = optarg; + break; + case 'w': + global_work_dir = optarg; + break; + case '?': + default: + fprintf(stderr, "Found this bad argument: %s\n", argv[optind]); + break; + } + } +} + +static void bad_exit(int sig) +{ + (void)sig; + if (global_op_file != NULL) + { + ibs_finalize(); + // Give the daemon some time to shut down and write its data. + usleep(3 * 1000 * IBS_WATCH_SELECT_TIMEOUT); + } + exit(EXIT_FAILURE); +} + +static void clean_exit(void *cpu_list, void *daemon_cpu_list) +{ + if (global_op_file != NULL) + { + ibs_finalize(); + // Give the daemon some time to shut down and write its data. + usleep(2 * 1000 * IBS_WATCH_SELECT_TIMEOUT); + } + if (cpu_list) + free(cpu_list); + if (daemon_cpu_list) + free(daemon_cpu_list); + exit(EXIT_SUCCESS); +} + +int fill_out_online_cores(int num_cpus, int num_online_cpus, char *cpu_list, + cpu_set_t *procs) +{ + FILE *online_fp = fopen("/sys/devices/system/cpu/online", "r"); + if (online_fp == NULL) + { + fprintf(stderr, "Could not open /sys/devices/system/cpu/online\n"); + exit(EXIT_FAILURE); + } + + char *online_cpus = NULL; + size_t len = 0; + ssize_t line_len = getline(&online_cpus, &len, online_fp); + if (line_len == -1 || len == 0) + { + fprintf(stderr, "Failed to read the number of online CPUs\n"); + exit(EXIT_FAILURE); + } + + int i = 0; + int previous_cpu = -1; + int num_online_seen = 0; + while (online_cpus[i] != '\0' && online_cpus[i] != '\n') + { + if (online_cpus[i] == ',') + { + i++; + continue; + } + else if (online_cpus[i] == '-') + { + // This only fills in the missing ones. The end-points + // are filled in during the normal numbers. + int next_cpu = online_cpus[i+1] - '0'; + for (int j = previous_cpu + 1; j < next_cpu; j++) + { + num_online_seen++; + cpu_list[j] = 1; + CPU_SET(j, procs); + } + previous_cpu = next_cpu; + } + else + { + int cpu_num = online_cpus[i] - '0'; + if (cpu_num < num_cpus) + { + num_online_seen++; + cpu_list[cpu_num] = 1; + CPU_SET(cpu_num, procs); + previous_cpu = cpu_num; + } + else + break; + } + i++; + if (num_online_seen >= num_online_cpus) + break; + } + fclose(online_fp); + return previous_cpu; +} + + +/* This application will take in another program to run, launch it on a subset of + * the cores on a system, and monitor it with IBS while it runs. One of the cores + * on the system is reserved for the tool that reads IBS samples and prints them + * to the screen -- the target application will not run there. + * This tool uses libIBS to perform this monitoring. This is an example + * application to show how to write a simple custom user-level IBS handler + * without needing to talk to the driver directly. + * If your IBS sample handlers (e.g. IBS_DAEMON_OP_WRITE function) are not + * heavyweight, you likely won't need the complexity of keeping a separate + * core for that work. */ +int main(int argc, char *argv[]) +{ + parse_args(argc, argv); + // Reset argv to real program + argv = &(argv[optind]); + + // Create the list of which CPUs to use for IBS sampling. + // Start by allocating a char array, one char for each possible CPU + int num_cpus = get_nprocs_conf(); + char *cpu_list = calloc(num_cpus, sizeof(char)); + char *daemon_cpu_list = calloc(num_cpus, sizeof(char)); + + // Now, how many CPUs are online? + int num_online_cpus = get_nprocs(); + if (num_online_cpus < 2) + { + fprintf(stderr, "ERROR. Online 1 CPU core is online.\n"); + fprintf(stderr, "We need one for the daemon and one for the app.\n"); + if (cpu_list) + { + free(cpu_list); + cpu_list = NULL; + } + if (daemon_cpu_list) + { + free(daemon_cpu_list); + daemon_cpu_list = NULL; + } + return EXIT_FAILURE; + } + cpu_set_t procs; + CPU_ZERO(&procs); + int last_online_core = fill_out_online_cores(num_cpus, num_online_cpus, + cpu_list, &procs); + if (last_online_core < 1) + { + fprintf(stderr, "Failed to get any good online cores.\n"); + fprintf(stderr, "Need at least two online cores!\n"); + if (cpu_list) + { + free(cpu_list); + cpu_list = NULL; + } + if (daemon_cpu_list) + { + free(daemon_cpu_list); + daemon_cpu_list = NULL; + } + return EXIT_FAILURE; + } + cpu_list[last_online_core] = 0; + CPU_CLR(last_online_core, &procs); + daemon_cpu_list[last_online_core] = 1; // Last CPU for IBS + + // If the user has set an output file for IBS on the command line, we + // should turn on all the IBS stuff. Otherwise, skip straight to the + // real work and avoid IBS entirely. + if (global_op_file != NULL) + { + int status, num_opts; + // Need to do this so that we build cleanly. Cast the function pointer + // for write_op_sample() into the type of pointer that we can pass in + // with IBS_DAEMON_OP_WRITE. + union + { + void (*write_op_fptr)(FILE *, ibs_op_t *); + void *ptr; + } func_ptr_cast; + func_ptr_cast.write_op_fptr = write_op_sample; + + ibs_option_list_t opts[] = { + // Turn on debug printing in this test program. + {IBS_DEBUG, (ibs_val_t)1}, + // Turn on IBS op sampling. + {IBS_OP, (ibs_val_t)1}, + // Set some various configuration parameters for taking IBS samples + {IBS_POLL_NUM_SAMPLES, (ibs_val_t)IBS_WATCH_SELECT_SAMPLES}, + {IBS_POLL_TIMEOUT, (ibs_val_t)IBS_WATCH_SELECT_TIMEOUT}, + {IBS_READ_ON_TIMEOUT, (ibs_val_t)0}, + {IBS_MAX_CNT, (ibs_val_t)IBS_WATCH_MAX_CNT}, + // Pass in the list of CPUs the actual application will run on, and + // thus the cores we should read IBS traces from. + {IBS_CPU_LIST, (ibs_val_t)cpu_list}, + {IBS_DAEMON_MAX_SAMPLES, (ibs_val_t)IBS_WATCH_MAX_SAMPLES}, + // Which core to run the IBS monitoring daemon on. + {IBS_DAEMON_CPU_LIST, (ibs_val_t)daemon_cpu_list}, + // File name to write op traces out to + {IBS_DAEMON_OP_FILE, (ibs_val_t)global_op_file}, + // Function that will write out op traces. + {IBS_DAEMON_OP_WRITE, (ibs_val_t)func_ptr_cast.ptr}, + }; + num_opts = sizeof(opts) / sizeof(ibs_option_list_t); + + // Once we've called initialize, attempts to exit this program should + // kill the daemon with ibs_finalize(), or it's going to sit around forever. + signal(SIGINT, bad_exit); + signal(SIGQUIT, bad_exit); + signal(SIGTERM, bad_exit); + signal(SIGHUP, bad_exit); + + status = ibs_initialize(opts, num_opts, 1); + if (status != 0) + { + if (cpu_list) + { + free(cpu_list); + cpu_list = NULL; + } + if (daemon_cpu_list) + { + free(daemon_cpu_list); + daemon_cpu_list = NULL; + } + return status; + } + } + + // Do the real work now. + launch_child_work(&procs, argv); + + // After the child has come back, stop the IBS daemon and quick. + clean_exit(cpu_list, daemon_cpu_list); + if (cpu_list) + { + free(cpu_list); + cpu_list = NULL; + } + if (daemon_cpu_list) + { + free(daemon_cpu_list); + daemon_cpu_list = NULL; + } + return EXIT_SUCCESS; +} + +``` + +`tools/ibs_daemon/run_ibs_daemon.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See tools/LICENSE for licensing details. +BASE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +if [ ! -f ${BASE_DIR}/ibs_daemon ]; then + echo -e "${BASE_DIR}/ibs_daemon does not exist. Exiting." + exit -1 +fi + +if ldd ${BASE_DIR}/ibs_daemon | grep -q "libibs.so => not found"; then + echo -e "libibs.so is not in the LD_LIBRARY_PATH. Trying to add it.." + if [ ! -f ${BASE_DIR}/../../lib/libibs.so ]; then + echo -e "${BASE_DIR}/../../lib/libibs.so does not exist. Trying to build it.." + pushd ${BASE_DIR}/../../lib/ + make + if [ $? -ne 0 ]; then + echo -e "Failed to build libibs.so. Exiting." + exit -1 + fi + popd + fi + export LD_LIBRARY_PATH=${BASE_DIR}/../../lib/:$LD_LIBRARY_PATH +fi + +if [ ! -f ${BASE_DIR}/../../lib/libibs.so ]; then + echo -e "Cannot find ${BASE_DIR}/../../lib/libibs.so. Exiting." + exit -1 +else + ${BASE_DIR}/ibs_daemon $* +fi + +``` + +`tools/ibs_decoder/Makefile`: + +``` +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See tools/LICENSE for licensing details. + +THIS_TOOL_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +THIS_TOOL_NAME := ibs_decoder +TOOL_CFLAGS+=-I $(LIB_DIR) + +include $(THIS_TOOL_DIR)../common.mk + +``` + +`tools/ibs_decoder/ibs_decoder.c`: + +```c +/* + * Copyright (C) 2015-2019 Advanced Micro Devices, Inc. + * + * This file is distributed under the BSD license described in tools/LICENSE + * + * This tool parses binary IBS op data from stdin to stdout in csv format. + * + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include "ibs-uapi.h" + +static int fam15h_model01h_err717 = 0; +static int fam14h_err484 = 0; + +static inline void print_u8(FILE *outf, uint8_t input) { + fprintf(outf, "%" PRIu8 ",", input); +} + +static inline void print_u16(FILE *outf, uint16_t input) { + fprintf(outf, "%" PRIu16 ",", input); +} + +static inline void print_u32(FILE *outf, uint32_t input) { + fprintf(outf, "%" PRIu32 ",", input); +} + +static inline void print_u64(FILE *outf, uint64_t input) { + fprintf(outf, "%" PRIu64 ",", input); +} + +static inline void print_x64(FILE *outf, uint64_t input) { + fprintf(outf, "0x%" PRIx64 ",", input); +} + +#define CHECK_ASPRINTF_RET(num_bytes) \ +{ \ + if (num_bytes <= 0) \ + { \ + fprintf(stderr, "asprintf in %s:%d failed\n", __FILE__, __LINE__); \ + exit(-1); \ + }\ +} +#define print_hdr(opf, fmt, ...) \ +{ \ + char *header_line; \ + int num_bytes = asprintf(&header_line, fmt, __VA_ARGS__); \ + CHECK_ASPRINTF_RET(num_bytes); \ + fwrite(header_line, 1, num_bytes, opf); \ + free(header_line); \ +} + +FILE *op_in_fp = NULL; +FILE *op_out_fp = NULL; +FILE *fetch_in_fp = NULL; +FILE *fetch_out_fp = NULL; + +void set_op_in_file(char *opt) +{ + op_in_fp = fopen(opt, "r"); + if (op_in_fp == NULL) { + fprintf(stderr, "Cannot fopen Op Input File: %s\n", opt); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +} + +void set_op_out_file(char *opt) +{ + op_out_fp = fopen(opt, "w"); + if (op_out_fp == NULL) { + fprintf(stderr, "Cannot fopen Op Output File: %s\n", opt); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +} + +void set_fetch_in_file(char *opt) +{ + fetch_in_fp = fopen(opt, "r"); + if (fetch_in_fp == NULL) { + fprintf(stderr, "Cannot fopen Fetch Input File: %s\n", opt); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +} + +void set_fetch_out_file(char *opt) +{ + fetch_out_fp = fopen(opt, "w"); + if (fetch_out_fp == NULL) { + fprintf(stderr, "Cannot fopen Fetch Output File: %s\n", opt); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +} + +void parse_args(int argc, char *argv[]) +{ + static struct option longopts[] = + { + {"op_in_file", required_argument, NULL, 'i'}, + {"op_out_file", required_argument, NULL, 'o'}, + {"fetch_in_file", required_argument, NULL, 'f'}, + {"fetch_out_file", required_argument, NULL, 'g'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + char c; + while ((c = getopt_long(argc, argv, "+hi:o:f:g:", longopts, NULL)) != -1) + { + switch (c) { + case 'h': + case '?': + fprintf(stderr, "This program parses IBS traces from the AMD Research IBS monitor.\n"); + fprintf(stderr, "It takes Op and Fetch traces (in separate files) and saves them to "); + fprintf(stderr, "human-readable CSV files with appropriate headers.\n"); + fprintf(stderr, "This is done as a separate progrma to reduce the monitor's overhead.\n"); + fprintf(stderr, "Usage: ./ibs_decoer [-o op_input] [-f fetch_input]\n"); + fprintf(stderr, "--op_in_file (or -i):\n"); + fprintf(stderr, " File with IBS op samples from the monitor program.\n"); + fprintf(stderr, "--op_out_file (or -o):\n"); + fprintf(stderr, " CSV file to output decoded IBS op trace.\n"); + fprintf(stderr, "--fetch_in_file (or -f):\n"); + fprintf(stderr, " File with IBS fetch samples from the monitor program.\n"); + fprintf(stderr, "--fetch_out_file (or -g):\n"); + fprintf(stderr, " CSV file to output decoded IBS fetch trace.\n"); + fprintf(stderr, "If you skip either of the input arguments, that IBS sample type will be ignored.\n"); + fprintf(stderr, "You cannot skip the *_out_file argument when you have an input file.\n\n"); + exit(EXIT_SUCCESS); + case 'i': + set_op_in_file(optarg); + break; + case 'o': + set_op_out_file(optarg); + break; + case 'f': + set_fetch_in_file(optarg); + break; + case 'g': + set_fetch_out_file(optarg); + break; + default: + fprintf(stderr, "Found this bad argument: %s\n", argv[optind]); + break; + } + } + + if (op_in_fp == NULL && fetch_in_fp == NULL) + { + fprintf(stderr, "\n\nWARNING. No input files given.\n\n"); + } + if (op_in_fp != NULL && op_out_fp == NULL) + { + fprintf(stderr, "\n\nERROR. There is an Op input file, "); + fprintf(stderr, "but no Op output file target.\n\n"); + exit(EXIT_FAILURE); + } + if (fetch_in_fp != NULL && fetch_out_fp == NULL) + { + fprintf(stderr, "\n\nERROR. There is a Fetch input file, "); + fprintf(stderr, "but no Fetch output file target.\n\n"); + exit(EXIT_FAILURE); + } +} + +#define header_parse(string, target) \ +if (!done_checking && !strncmp(line, string, sizeof(string)-1))\ +{\ + char *token = strtok(line, split);\ + if (token != NULL)\ + token = strtok(NULL, split);\ + if (token != NULL)\ + *target = strtoul(token, NULL, 0);\ + done_checking = 1;\ +} +void parse_op_in_header(uint32_t *family, uint32_t *model, int *brn_resync, + int *misp_return, int *brn_target, int *op_cnt_ext, + int *rip_invalid_chk, int *op_brn_fuse, int *ibs_op_data_4, + int *microcode, int *ibs_op_data2_4_5, int *dc_ld_bnk_con, + int *dc_st_bnk_con, int *dc_st_to_ld_fwd, int *dc_st_to_ld_can, + int *ibs_data3_20_31_48_63) +{ + char line[256]; + memset(line, 0, sizeof(line)); + // Reading this line marks the end of the header + while (strncmp(line, "=============================================", + sizeof("=============================================")-1)) + { + const char split[2] = ":"; + if (!fgets(line, 255, op_in_fp)) + break; + if (!strncmp(line, "=============================================", + sizeof("=============================================")-1)) + break; + + int done_checking = 0; + header_parse("AMD Processor Family:", family); + header_parse("AMD Processor Model:", model); + header_parse("IbsOpBrnResync:", brn_resync); + header_parse("IbsOpMispReturn:", misp_return); + header_parse("BrnTrgt:", brn_target); + header_parse("OpCntExt:", op_cnt_ext); + header_parse("RipInvalidChk:", rip_invalid_chk); + header_parse("OpBrnFuse:", op_brn_fuse); + header_parse("IbsOpData4:", ibs_op_data_4); + header_parse("Microcode:", microcode); + header_parse("IBSOpData2_4_5:", ibs_op_data2_4_5); + header_parse("IbsDcLdBnkCon:", dc_ld_bnk_con); + header_parse("IbsDcStBnkCon:", dc_st_bnk_con); + header_parse("IbsDcStToLdFwd:", dc_st_to_ld_fwd); + header_parse("IbsDcStToLdCan:", dc_st_to_ld_can); + header_parse("IbsData3_20_31_48_63:", ibs_data3_20_31_48_63); + } + + if (*family == 0x15 && *model <= 0x1) + fam15h_model01h_err717 = 1; + if (*family == 0x14) + fam14h_err484 = 1; + +} + +void parse_fetch_in_header(uint32_t *family, uint32_t *model, + int *fetch_ctl_ext) +{ + char line[256]; + memset(line, 0, sizeof(line)); + // Reading this line marks the end of the header + while (strncmp(line, "=============================================", + sizeof("=============================================")-1)) + { + const char split[2] = ":"; + if (!fgets(line, 255, fetch_in_fp)) + break; + if (!strncmp(line, "=============================================", + sizeof("=============================================")-1)) + break; + + int done_checking = 0; + header_parse("AMD Processor Family:", family); + header_parse("AMD Processor Model:", model); + header_parse("IbsFetchCtlExtd:", fetch_ctl_ext); + } +} + +static void output_common_header(FILE *outf) +{ + print_hdr(outf, "%s,%s,%s,%s,%s,", "TSC", "CPU_Number", + "TID", "PID", "Kern_mode"); +} + +static void output_op_header(FILE *outf, uint32_t fam, uint32_t model, + int brn_resync, int misp_return, int brn_trgt, int op_cnt_ext, + int rip_invalid_chk, int op_brn_fuse, int ibs_op_data_4, int microcode, + int ibs_op_data2_4_5, int dc_ld_bnk_con, int dc_st_bnk_con, + int dc_st_to_ld_fwd, int dc_st_to_ld_can, int ibs_data3_20_31_48_63) +{ + // Basic things for each IBS sample. TSC when it was taken, CPU number, + // PID, TID, and user/kernel mode. + output_common_header(outf); + + // IbsOpRip / IBS_OP_RIP + print_hdr(outf, "%s,", "IbsOpRip"); + + // Data on sampling rate + if (op_cnt_ext) + { + print_hdr(outf, "%s,", "IbsOpMaxCnt[26:0]"); + } + else + { + print_hdr(outf, "%s,", "IbsOpMaxCnt[19:0]"); + } + + // IbsOpData / IBS_OP_DATA + print_hdr(outf, "%s,%s,", "IbsCompToRetCtr", "IbsTagToRetCtr"); + if (brn_resync) + print_hdr(outf, "%s,", "IbsOpBrnResync"); + if (misp_return) + print_hdr(outf, "%s,", "IbsOpMispReturn"); + print_hdr(outf, "%s,%s,%s,%s,", "IbsOpReturn", "IbsOpBrnTaken", "IbsOpBrnMisp", + "IbsOpBrnRet"); + if (rip_invalid_chk) + print_hdr(outf, "%s,", "IbsRipInvalid"); + if (op_brn_fuse) + print_hdr(outf, "%s,", "IbsOpBrnFuse"); + if (microcode) + print_hdr(outf, "%s,", "IbsOpMicrocode"); + + // IbsOpData2 / IBS_OP_DATA2 + if (fam < 0x17) + { + print_hdr(outf, "%s,", "NbIbsReqSrc"); + } + else + { + print_hdr(outf, "%s,", "DataSrc"); + } + + if (ibs_op_data2_4_5) + { + if (fam < 0x17) + { + print_hdr(outf, "%s,%s,", "NbIbsReqDstNode", "NbIbsReqCacheHitSt"); + } + else + { + print_hdr(outf, "%s,%s,", "RmtNode", "CacheHitSt"); + } + } + + // IbsOpData3 / IBS_OP_DATA3 + print_hdr(outf, "%s,%s,%s,%s,%s,%s,%s,%s,%s,", "IbsLdOp", "IbsStOp", + "IbsDcL1tlbMiss", "IbsDcL2TlbMiss", "IbsDcL1TlbHit2M", + "IbsDcL1TlbHit1G", "IbsDcL2tlbHit2M", "IbsDcMiss", "IbsDcMissAcc"); + if (dc_ld_bnk_con) + { + print_hdr(outf, "%s,", "IbsDcLdBnkCon"); + } + if (dc_st_bnk_con) + { + print_hdr(outf, "%s,", "IbsDcStBnkCon"); + } + if (dc_st_to_ld_fwd) + { + print_hdr(outf, "%s,", "IbsDcStToLdFwd"); + } + if (dc_st_to_ld_can) + { + print_hdr(outf, "%s,", "IbsDcStToLdCan"); + } + print_hdr(outf, "%s,%s,%s,", "IbsDcWcMemAcc", "IbsDcUcMemAcc", + "IbsDcLockedOp"); + if (fam <= 0x12 || (fam == 0x15 && model < 0x20)) + { + print_hdr(outf, "%s,", "IbsDcMabHit"); + } + else + { + print_hdr(outf, "%s,", "DcMissNoMabAlloc"); + } + print_hdr(outf, "%s,%s,%s,", "IbsDcLinAddrValid", "IbsDcPhyAddrValid", + "IbsDcL2tlbHit1G"); + if (ibs_data3_20_31_48_63) + { + print_hdr(outf, "%s,%s,%s,%s,", "IbsL2Miss", "IbsSwPf", + "IbsOpMemWidth", "IbsOpDcMissOpenMemReqs"); + } + print_hdr(outf, "%s,", "IbsDcMissLat"); + if (ibs_data3_20_31_48_63) + { + print_hdr(outf, "%s,", "IbstlbRefillLat"); + } + + // IbsDcLinAd / IBS_DC_LINADDR + print_hdr(outf, "%s,", "IbsDcLinAd"); + + // IbsDcPhsAd / IBS_DC_PHYSADDR + print_hdr(outf, "%s,", "IbsDcPhysAd"); + + if (brn_trgt) + { + print_hdr(outf, "%s,", "IbsBrnTarget"); + } + + if (ibs_op_data_4) + { + print_hdr(outf, "%s,", "IbsOpLdResync"); + } + + print_hdr(outf, "%s", "\n"); +} + +static void output_fetch_header(FILE *outf, uint32_t fam, uint32_t model, + int fetch_ctl_ext) +{ + // Basic things for each IBS sample. TSC when it was taken, CPU number, + // PID, TID, and user/kernel mode. + output_common_header(outf); + + // IBS_FETCH_CTL_PHYADDR_VALID, IBS_DC_LINADDR, and IBS_DC_PHYSADDR + print_hdr(outf, "%s,%s,%s,", "IbsPhyAddrValid", "IbsFetchLinAd", + "IbsFetchPhysAd"); + + // IbsFetchCtl / IBS_FETCH_CTL + // Every generation of IBS Fetch has these columns. + print_hdr(outf, "%s,%s,%s,%s,%s,%s,%s,", "IbsFetchMaxCnt[19:0]", + "IbsFetchLat", "IbsFetchComp", "IbsIcMiss", + "IbsL1TlbPgSz", "IbsL1TlbMiss", "IbsL2TlbMiss"); + + // Only CZ, ST, and ZN have this field, but there is no CPUID for it. + if ((fam == 0x15 && model >= 0x60) || fam == 0x17) + print_hdr(outf, "%s,", "IbsFetchL2Miss"); + + // IBS_EXTD_CTL + if (fetch_ctl_ext) + print_hdr(outf, "%s,", "IbsItlbRefillLat"); + + print_hdr(outf, "%s", "\n"); +} + +void output_op_entry(FILE *outf, ibs_op_t op, uint32_t family, + uint32_t model, int brn_resync, int misp_return, int brn_trgt, + int op_cnt_ext, int rip_invalid_chk, int op_brn_fuse, + int ibs_op_data_4, int microcode, int ibs_op_data2_4_5, + int dc_ld_bnk_con, int dc_st_bnk_con, int dc_st_to_ld_fwd, + int dc_st_to_ld_can, int ibs_data3_20_31_48_63) +{ + // Common stuff + print_u64(outf, op.tsc); + fprintf(outf, "%d,%d,%d,%d,", op.cpu, op.tid, op.pid, + op.kern_mode); + + // IbsOpRip / IBS_OP_RIP + print_x64(outf, op.op_rip); + + // Data on sampling rate + uint32_t op_max_cnt = 0; + if (op_cnt_ext) + op_max_cnt = op.op_ctl.reg.ibs_op_max_cnt_upper << 20; + op_max_cnt += (op.op_ctl.reg.ibs_op_max_cnt << 4); + print_u32(outf, op_max_cnt); + + // IbsOpData / IBS_OP_DATA + print_u16(outf, op.op_data.reg.ibs_comp_to_ret_ctr); + print_u16(outf, op.op_data.reg.ibs_tag_to_ret_ctr); + if (brn_resync) + print_u8(outf, op.op_data.reg.ibs_op_brn_resync); + if (misp_return) + print_u8(outf, op.op_data.reg.ibs_op_misp_return); + print_u8(outf, op.op_data.reg.ibs_op_brn_ret); + print_u8(outf, op.op_data.reg.ibs_op_brn_taken); + print_u8(outf, op.op_data.reg.ibs_op_brn_misp); + print_u8(outf, op.op_data.reg.ibs_op_brn_ret); + if (rip_invalid_chk) + print_u8(outf, op.op_data.reg.ibs_rip_invalid); + if (op_brn_fuse) + print_u8(outf, op.op_data.reg.ibs_op_brn_fuse); + if (microcode) + print_u8(outf, op.op_data.reg.ibs_op_microcode); + + // IbsOpData2 / IBS_OP_DATA2 + // This register is only valid for load operations that miss in + // both the L1 and L2. However, until KV+ and BT+, IBS could not + // tell us if an access was an L2 miss. + // The first case, we support checking L2, so check all the three things. + // In the second case, we can't read L2, so go ahead if it's a load + // L1 miss + if ((ibs_data3_20_31_48_63 && op.op_data3.reg.ibs_ld_op && + op.op_data3.reg.ibs_l2_miss && op.op_data3.reg.ibs_dc_miss) + || + (!ibs_data3_20_31_48_63 && op.op_data3.reg.ibs_ld_op && + op.op_data3.reg.ibs_dc_miss)) + { + if (fam14h_err484 && op.op_data3.reg.ibs_dc_wc_mem_acc) + fprintf(outf, "-,-,-,"); + else + { + switch (op.op_data2.reg.ibs_nb_req_src) { + case 0: + fprintf(outf, "-,"); + break; + case 1: + if (family == 0x10 || (family == 0x15 && model < 0x10)) + fprintf(outf, "local_L3,"); + else + { + fprintf(outf, "Reserved-%" PRIu8 ",", + op.op_data2.reg.ibs_nb_req_src); + } + break; + case 2: + fprintf(outf, "other_core_cache,"); + break; + case 3: + fprintf(outf, "DRAM,"); + break; + case 7: + fprintf(outf, "Other,"); + break; + default: + fprintf(outf, "Reserved-%" PRIu8 ",", + op.op_data2.reg.ibs_nb_req_src); + break; + } + if (ibs_op_data2_4_5) + { + // This is only valid if the NbIbsReqSrc != 0 + if (op.op_data2.reg.ibs_nb_req_src == 0) + fprintf(outf, "-,"); + else if (op.op_data2.reg.ibs_nb_req_dst_node == 1) + fprintf(outf, "other_node,"); + else + fprintf(outf, "same_node,"); + + // This is only valid when NbIbsReqSrc == 2 + if (op.op_data2.reg.ibs_nb_req_src != 2) + fprintf(outf, "-,"); + else if (op.op_data2.reg.ibs_nb_req_cache_hit_st == 1) + fprintf(outf, "O,"); + else + fprintf(outf, "M,"); + } + } + } + else + fprintf(outf, "-,-,-,"); + + // IbsOpData3 / IBS_OP_DATA3 + print_u8(outf, op.op_data3.reg.ibs_ld_op); + print_u8(outf, op.op_data3.reg.ibs_st_op); + print_u8(outf, op.op_data3.reg.ibs_dc_l1_tlb_miss); + print_u8(outf, op.op_data3.reg.ibs_dc_l2_tlb_miss); + print_u8(outf, op.op_data3.reg.ibs_dc_l1_tlb_hit_2m); + print_u8(outf, op.op_data3.reg.ibs_dc_l1_tlb_hit_1g); + print_u8(outf, op.op_data3.reg.ibs_dc_l2_tlb_hit_2m); + print_u8(outf, op.op_data3.reg.ibs_dc_miss); + print_u8(outf, op.op_data3.reg.ibs_dc_miss_acc); + if (dc_ld_bnk_con) + print_u8(outf, op.op_data3.reg.ibs_dc_ld_bank_con); + if (dc_st_bnk_con) + print_u8(outf, op.op_data3.reg.ibs_dc_st_bank_con); + if (dc_st_to_ld_fwd) + print_u8(outf, op.op_data3.reg.ibs_dc_st_to_ld_fwd); + if (dc_st_to_ld_can) + print_u8(outf, op.op_data3.reg.ibs_dc_st_to_ld_can); + print_u8(outf, op.op_data3.reg.ibs_dc_wc_mem_acc); + print_u8(outf, op.op_data3.reg.ibs_dc_uc_mem_acc); + print_u8(outf, op.op_data3.reg.ibs_dc_locked_op); + if (fam15h_model01h_err717 && (op.op_data3.reg.ibs_dc_miss -= 0)) + print_u8(outf, 0); + else + print_u8(outf, op.op_data3.reg.ibs_dc_no_mab_alloc); + print_u8(outf, op.op_data3.reg.ibs_lin_addr_valid); + print_u8(outf, op.op_data3.reg.ibs_phy_addr_valid); + print_u8(outf, op.op_data3.reg.ibs_dc_l2_tlb_hit_1g); + if (ibs_data3_20_31_48_63) + { + print_u8(outf, op.op_data3.reg.ibs_l2_miss); + print_u8(outf, op.op_data3.reg.ibs_sw_pf); + switch(op.op_data3.reg.ibs_op_mem_width) + { + case 0: + fprintf(outf, "0,"); + break; + case 1: + fprintf(outf, "1,"); + break; + case 2: + fprintf(outf, "2,"); + break; + case 3: + fprintf(outf, "4,"); + break; + case 4: + fprintf(outf, "8,"); + break; + case 5: + fprintf(outf, "16,"); + break; + default: + fprintf(outf, "Reserved-%" PRIu8 ",", + op.op_data3.reg.ibs_op_mem_width); + break; + } + print_u8(outf, op.op_data3.reg.ibs_op_dc_miss_open_mem_reqs); + } + print_u16(outf, op.op_data3.reg.ibs_dc_miss_lat); + if (ibs_data3_20_31_48_63) + print_u16(outf, op.op_data3.reg.ibs_tlb_refill_lat); + + // IbsDcLinAd / IBS_DC_LINADDR + if (op.op_data3.reg.ibs_lin_addr_valid) + print_x64(outf, op.dc_lin_ad); + else + fprintf(outf, "-,"); + + // IbsDcPhsAd / IBS_DC_PHYSADDR + if (op.op_data3.reg.ibs_phy_addr_valid) + print_x64(outf, op.dc_phys_ad.reg.ibs_dc_phys_addr); + else + fprintf(outf, "-,"); + + if (brn_trgt) + { + if (op.op_data.reg.ibs_op_brn_ret) + print_x64(outf, op.br_target); + else + fprintf(outf, "-,"); + } + + if (ibs_op_data_4) + print_u8(outf, op.op_data4.reg.ibs_op_ld_resync); + + fprintf(outf, "\n"); +} + +static void output_fetch_entry(FILE *outf, ibs_fetch_t fetch, + uint32_t family, uint32_t model, int fetch_ctl_ext) +{ + // Common stuff + print_u64(outf, fetch.tsc); + fprintf(outf, "%d,%d,%d,%d,", fetch.cpu, fetch.tid, fetch.pid, + fetch.kern_mode); + + // IBS_FETCH_CTL_PHYADDR_VALID, IBS_DC_LINADDR, and IBS_DC_PHYSADDR + print_u8(outf, fetch.fetch_ctl.reg.ibs_phy_addr_valid); + print_x64(outf, fetch.fetch_lin_ad); + if (fetch.fetch_ctl.reg.ibs_phy_addr_valid) + print_x64(outf, fetch.fetch_phys_ad.reg.ibs_fetch_phy_addr); + else + fprintf(outf, "-,"); + + // IbsFetchCtl / IBS_FETCH_CTL + // Every generation of IBS Fetch has these columns. + uint32_t fetch_max_cnt = (fetch.fetch_ctl.reg.ibs_fetch_max_cnt << 4); + print_u32(outf, fetch_max_cnt); + + print_u16(outf, fetch.fetch_ctl.reg.ibs_fetch_lat); + print_u8(outf, fetch.fetch_ctl.reg.ibs_fetch_comp); + print_u8(outf, fetch.fetch_ctl.reg.ibs_ic_miss); + + if (fetch.fetch_ctl.reg.ibs_phy_addr_valid) + { + switch (fetch.fetch_ctl.reg.ibs_l1_tlb_pg_sz) { + case 0: + fprintf(outf, "4 KB,"); + break; + case 1: + fprintf(outf, "2 MB,"); + break; + case 2: + fprintf(outf, "1 GB,"); + break; + case 3: + if (family == 0x17) + { + fprintf(outf, "16 KB,"); + break; + } + /* Fallthrough */ + default: + fprintf(outf, "Reserved-%" PRIu8 ",", + fetch.fetch_ctl.reg.ibs_l1_tlb_pg_sz); + break; + } + } + else + fprintf(outf, "-,"); + + print_u8(outf, fetch.fetch_ctl.reg.ibs_l1_tlb_miss); + print_u8(outf, fetch.fetch_ctl.reg.ibs_l2_tlb_miss); + + // Only CZ, ST, and ZN have this field, but there is no CPUID for it. + if ((family == 0x15 && model >= 0x60) || family == 0x17) + { + print_u8(outf, fetch.fetch_ctl.reg.ibs_fetch_l2_miss); + } + + // IBS_EXTD_CTL + if (fetch_ctl_ext) + { + if (fetch.fetch_ctl.reg.ibs_fetch_comp) + print_u16(outf, fetch.fetch_ctl_extd.reg.ibs_itlb_refill_lat); + else + fprintf(outf, "-,"); + } + + print_hdr(outf, "%s", "\n"); +} + +void do_op_work(void) +{ + uint32_t family = 0, model = 0; + int brn_resync = 0, misp_return = 0, brn_trgt = 0, op_cnt_ext = 0; + int rip_invalid_chk = 0, op_brn_fuse = 0, ibs_op_data_4 = 0, microcode = 0; + int ibs_op_data2_4_5 = 0, dc_ld_bnk_con = 0, dc_st_bnk_con = 0; + int dc_st_to_ld_fwd = 0, dc_st_to_ld_can = 0, ibs_data3_20_31_48_63 = 0; + + printf("Beginning decode of IBS Op Trace header..."); + parse_op_in_header(&family, &model, &brn_resync, &misp_return, &brn_trgt, + &op_cnt_ext, &rip_invalid_chk, &op_brn_fuse, &ibs_op_data_4, + µcode, &ibs_op_data2_4_5, &dc_ld_bnk_con, &dc_st_bnk_con, + &dc_st_to_ld_fwd, &dc_st_to_ld_can, &ibs_data3_20_31_48_63); + printf("Done!\n"); + + output_op_header(op_out_fp, family, model, brn_resync, misp_return, + brn_trgt, op_cnt_ext, rip_invalid_chk, op_brn_fuse, ibs_op_data_4, + microcode, ibs_op_data2_4_5, dc_ld_bnk_con, dc_st_bnk_con, + dc_st_to_ld_fwd, dc_st_to_ld_can, ibs_data3_20_31_48_63); + + ibs_op_t op; + uint64_t num_samples_seen = 0; + printf("Starting to decode op trace. This may take a while...\n"); + while (fread((char *)&op, sizeof(op), 1, op_in_fp) > 0) { + num_samples_seen++; + if (num_samples_seen % 100000 == 0) + { + printf("Working on op sample number %" PRIu64 "...\n", + num_samples_seen); + } + + output_op_entry(op_out_fp, op, family, model, brn_resync, misp_return, + brn_trgt, op_cnt_ext, rip_invalid_chk, op_brn_fuse, + ibs_op_data_4, microcode, ibs_op_data2_4_5, dc_ld_bnk_con, + dc_st_bnk_con, dc_st_to_ld_fwd, dc_st_to_ld_can, + ibs_data3_20_31_48_63); + } + printf("Done with op samples!\n"); +} + +void do_fetch_work(void) +{ + uint32_t family = 0, model = 0; + int fetch_ctl_ext = 0; + + printf("Beginning decode of IBS Fetch Trace header..."); + parse_fetch_in_header(&family, &model, &fetch_ctl_ext); + printf("Done!\n"); + + output_fetch_header(fetch_out_fp, family, model, fetch_ctl_ext); + + ibs_fetch_t fetch; + uint64_t num_samples_seen = 0; + printf("Starting to decode fetch trace. This may take a while...\n"); + while (fread((char *)&fetch, sizeof(fetch), 1, fetch_in_fp) > 0) { + num_samples_seen++; + if (num_samples_seen % 100000 == 0) + { + printf("Working on fetch sample number %" PRIu64 "...\n", + num_samples_seen); + } + + output_fetch_entry(fetch_out_fp, fetch, family, model, fetch_ctl_ext); + } + printf("Done with fetch samples!\n"); +} + +int main(int argc, char *argv[]) { + parse_args(argc, argv); + + if (op_in_fp != NULL) + do_op_work(); + + if (fetch_in_fp != NULL) + do_fetch_work(); + + printf("Decoding complete. Exiting application.\n\n"); + exit(EXIT_SUCCESS); +} + +``` + +`tools/ibs_decoder/ibs_op_csv2Rdata.R`: + +```R +# Copyright (C) 2015-2017 Advanced Micro Devices, Inc. +# +# This file is distributed under the BSD license described in tools/COPYING +# +# This Rscript saves big IBS op csv files as smaller Rdata objects. +# +# Usage: +# Rscript op_csv2Rdata.R file1 [file2 ...] +# Read file1.csv [, file2.csv, ...] and output file1.Rdata [, file2.Rdata, ...]. + +# Use this function to read in data when the CSV is too big for read.csv, but the data isn't. +# Basic idea is to read a 'chunk' of a big csv at a time, and add it to the data frame. +assign.from.chunky.csv <- function(var.name, file, chunk.rows) +{ + assign(var.name, read.csv(file, nrows=chunk.rows), inherits=TRUE) + tmp.nrows.read <- nrow(get(var.name)) + tmp.skipped.count <- 0 + print(nrow(get(var.name))) # Print progress + while (tmp.nrows.read == chunk.rows) { + tmp.skipped.count <- tmp.skipped.count + 1 + tmp.df <- read.csv(file, skip=(chunk.rows * tmp.skipped.count), nrows=chunk.rows) + tmp.nrows.read <- nrow(tmp.df) + colnames(tmp.df) <- colnames(get(var.name)) + assign(var.name, rbind(get(var.name), tmp.df), inherits=TRUE) + print(nrow(get(var.name))) # Print progress + rm(tmp.df) + } + rm(tmp.nrows.read) + rm(tmp.skipped.count) +} + +# Read CSV files and save the R data frames +for (name in commandArgs(TRUE)) { + var.name <- paste('op.', name, sep='') + print(var.name) + print(system.time( + assign.from.chunky.csv(var.name, paste(name, '.csv', sep=''), 2e6) + )) + save(list=var.name, file=paste(name, '.Rdata', sep='')) + rm(list=var.name) + gc() +} + +``` + +`tools/ibs_monitor/Makefile`: + +``` +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See tools/LICENSE for licensing details. + +THIS_TOOL_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +THIS_TOOL_NAME := ibs_monitor + +include $(THIS_TOOL_DIR)../common.mk + +``` + +`tools/ibs_monitor/cpu_check.c`: + +```c +/* + * Copyright (C) 2015-2018 Advanced Micro Devices, Inc. + * + * This file is distributed under the BSD license described in tools/LICENSE + * + * A series of functions to detect CPU features for the AMD Research IBS + * monitoring utility + */ +#include +#include +#include +#include + +// Function for getting CPUID values -- useful for getting the CPU family and +// testing which features our IBS system on this processor supports. +static void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) +{ + asm volatile("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) + : "0" (*eax), "2" (*ecx)); +} + +uint32_t cpu_family(void) +{ + uint32_t eax = 0x1; + uint32_t ebx = 0, ecx = 0, edx = 0; + cpuid(&eax, &ebx, &ecx, &edx); + uint32_t family = (eax & 0xf00) >> 8; + uint32_t ext_family = (eax & 0xff00000) >> 20; + return family+ext_family; +} + +uint32_t cpu_model(void) +{ + uint32_t eax = 0x1; + uint32_t ebx = 0, ecx = 0, edx = 0; + cpuid(&eax, &ebx, &ecx, &edx); + uint32_t base_model = (eax & 0xf0) >> 4; + uint32_t ext_model = (eax & 0xf0000) >> 16; + return (ext_model << 4) | base_model; +} + +uint32_t cpu_stepping(void) +{ + uint32_t eax = 0x1; + uint32_t ebx = 0, ecx = 0, edx = 0; + cpuid(&eax, &ebx, &ecx, &edx); + return (eax & 0xf); +} + +char *cpu_name(void) +{ + char * ret_str = calloc(49, 1); + if (ret_str != NULL) + { + uint32_t eax = 0x80000002; + uint32_t ebx = 0, ecx = 0, edx = 0; + cpuid(&eax, &ebx, &ecx, &edx); + memcpy(ret_str, &eax, 4); + memcpy(ret_str + 4, &ebx, 4); + memcpy(ret_str + 8, &ecx, 4); + memcpy(ret_str + 12, &edx, 4); + eax = 0x80000003; + ebx = ecx = edx = 0; + cpuid(&eax, &ebx, &ecx, &edx); + memcpy(ret_str + 16, &eax, 4); + memcpy(ret_str + 20, &ebx, 4); + memcpy(ret_str + 24, &ecx, 4); + memcpy(ret_str + 28, &edx, 4); + eax = 0x80000004; + ebx = ecx = edx = 0; + cpuid(&eax, &ebx, &ecx, &edx); + memcpy(ret_str + 32, &eax, 4); + memcpy(ret_str + 36, &ebx, 4); + memcpy(ret_str + 40, &ecx, 4); + memcpy(ret_str + 44, &edx, 4); + ret_str[48] = '\0'; + } + else + { + fprintf(stderr, "ERROR. Can't calloc string at %s:%d\n", + __FILE__, __LINE__); + exit(EXIT_FAILURE); + } + return ret_str; +} + +// This function reads the Instruction Based SAmpling Identifies out of CPUID +uint32_t get_deep_ibs_info(void) +{ + uint32_t eax = 0x8000001b; + uint32_t ebx = 0, ecx = 0, edx = 0; + cpuid(&eax, &ebx, &ecx, &edx); + return eax; +} + +// Check CPUID_Fn0000_0000 EBX, ECX, EDX for "AuthenticAMD" +void check_amd_processor(void) +{ + uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0; + cpuid(&eax, &ebx, &ecx, &edx); + if (ebx != 0x68747541 || ecx != 0x444D4163 || edx != 0x69746E65) + { + char print_buf[13]; + memcpy(print_buf, &ebx, 4); + memcpy(print_buf + 4, &edx, 4); + memcpy(print_buf + 8, &ecx, 4); + print_buf[12] = '\0'; + fprintf(stderr, "ERROR. This is apparently not an AMD processor.\n"); + fprintf(stderr, " Processor vendor string: %s\n", print_buf); + fprintf(stderr, " Should be: AuthenticAMD\n"); + exit(EXIT_FAILURE); + } +} + +void check_basic_ibs_support(void) +{ + check_amd_processor(); + // Check for Family 10h before trying to read the IBS CPUID registers. + uint32_t fam = cpu_family(); + if (fam < 0x10 || fam == 0x11) + { + fprintf(stderr, "ERROR. AMD processor family is 0x%x\n", fam); + fprintf(stderr, " Family 0x10 or above is required for IBS.\n"); + fprintf(stderr, " (Except Family 0x11 -- that is unupported)\n"); + exit(EXIT_FAILURE); + } + + // Read the IBS bit out of Feature Identifiers in CPUID + uint32_t eax = 0x80000001; + uint32_t ebx = 0, ecx = 0, edx = 0; + cpuid(&eax, &ebx, &ecx, &edx); + int ibs_support = ecx & (1 << 10); + ibs_support >>= 10; + // Family 17h processors with first-generation cores (previously code-named + // "Zen") will not claim IBS support without a BIOS setting, but our driver + // can enable the proper settings to turn on IBS. The driver will turn on + // this bit, so if it's not set, we should fail out. + if (!ibs_support) + { + // If bit 10 of CPUID_Fn8000_0001_ECX is not set, then IBS is disabled + fprintf(stderr, "ERROR. CPUID says no IBS support.\n"); + fprintf(stderr, " CPUID_Fn8000_0001_ECX bit 10 is: %d\n", + ibs_support); + exit(EXIT_FAILURE); + } + + // Read the IBS feature flag valid bits in the IBS ID CPUID + uint32_t ibs_id = get_deep_ibs_info(); + ibs_id &= 1; + if (!ibs_id) + { + fprintf(stderr, "ERROR. CPUID says IBS FV is not valid.\n"); + fprintf(stderr, "The low-order bit in CPUID Fn8000_001B is 0\n"); + exit(EXIT_FAILURE); + } +} + +void check_ibs_op_support(void) +{ + // Check OpSam and OpCnt bits in the IBS ID register to make sure we can do + // the appropriate IBS work for ops. + uint32_t ibs_id = get_deep_ibs_info(); + uint32_t op_sam = (ibs_id & (1 << 2)) >> 2; + uint32_t op_cnt = (ibs_id & (1 << 4)) >> 4; + if (!op_sam || !op_cnt) + { + fprintf(stderr, "ERROR. Cannot perform op sampling according to CPUID.\n"); + fprintf(stderr, " CPUID_Fn8000_001B_EAX[OpSam] = %u\n", op_sam); + fprintf(stderr, " CPUID_Fn8000_001B_EAX[OpCnt] = %u\n", op_cnt); + exit(EXIT_FAILURE); + } +} + +void check_ibs_fetch_support(void) +{ + // Check FetchSam to make sure we can properly do fetch sampling. + uint32_t ibs_id = get_deep_ibs_info(); + uint32_t fetch_sam = (ibs_id & (1 << 1)) >> 1; + if (!fetch_sam) + { + fprintf(stderr, "ERROR. Cannot fetch sample according to CPUID.\n"); + fprintf(stderr, " CPUID_Fn8000_001B_EAX[FetchSam] = %u\n", fetch_sam); + exit(EXIT_FAILURE); + } +} + +``` + +`tools/ibs_monitor/cpu_check.h`: + +```h +/* + * Copyright (C) 2017 Advanced Micro Devices, Inc. + * + * This file is distributed under the BSD license described in tools/LICENSE + */ +#ifndef CPU_CHECK_H +#define CPU_CHECK_H + +// Find and return the CPU family, model number, or stepping using CPUID +uint32_t cpu_family(void); +uint32_t cpu_model(void); +uint32_t cpu_stepping(void); + +// Gather the CPU name out of the CPUID system. This will return a newly +// allocated array. It is the callers responsibility to free this array. +char *cpu_name(void); + +// Return the IBS information contained in CPUID Fn8000_0001_EAX. +uint32_t get_deep_ibs_info(void); + +// The following functions will check for IBS support by looking at: +// 1) Whether this is an AMD processor +// 2) Whether the processor indicates IBS support in CPUID Fn0000_0001 +// 3) Whether it indicates IBS op/fetch support in CPUID Fn8000_0001 +// If they detect that they do not support IBS, they will exit the program. +void check_amd_processor(void); +void check_basic_ibs_support(void); +void check_ibs_op_support(void); +void check_ibs_fetch_support(void); + + +#endif /* CPU_CHECK_H */ + +``` + +`tools/ibs_monitor/ibs_monitor.c`: + +```c +/* + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This file is distributed under the BSD license described in tools/LICENSE + * + * This program executes another program and collects performance information + * from IBS during its execution. It dumps the *raw* IBS sample data structures + * to the file system. We purposefully avoid ecoding the IBS samples until + * later, so that the decode work does not disturb the application under test. + * + * Usage: ./ibs_monitor [-o op_output] [-f fetch_output] program [...] + * Arguments: + * (1) op_output: file to which to save IBS op samples + * (2) fetch_output:file to which to save fetch samples + * (3...) program: program to run and monitor plus any of its arguments + */ + +// Add the _GNU_SOURCE define to allow asprintf +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ibs-uapi.h" +#include "ibs_monitor.h" +#include "cpu_check.h" + +// Note that this program does not use libIBS. This is an example of a program +// that directly talks to the AMD Research IBS driver using the ioctl() +// controls in ibs-uapi.h. + +// Counters to keep track of the number of IBS samples from Ops and Fetches +unsigned long n_op_samples = 0; +unsigned long n_fetch_samples = 0; + +// These counters will keep track of the number of IBS samples that were +// taken by the kernel driver, but were overwritten because the user-level +// application did not read out the data fast enough. +// Either increase the sampling rate or increase the buffer size to +// avoid these lost samples. +unsigned long n_lost_op_samples = 0; +unsigned long n_lost_fetch_samples = 0; + +// Global variables for IBS driver settings +int op_cnt_max_to_set = 0; +int fetch_cnt_max_to_set = 0; +int buffer_size = 0; +char *global_buffer = NULL; +int poll_percent = 0; +int poll_size = 0; +int poll_timeout = 0; +char *global_work_dir = NULL; +char *ld_debug_out = NULL; + +void set_global_defaults(void) +{ + op_cnt_max_to_set = OP_MAX_CNT; + fetch_cnt_max_to_set = FETCH_MAX_CNT; + buffer_size = BUFFER_SIZE_B; + poll_percent = POLL_SIZE_PERCENT; + poll_size = buffer_size * ((float)poll_percent / 100.);; + poll_timeout = POLL_TIMEOUT; +} + +void set_op_file(char *opt, FILE **opf, int *flavors) +{ + if (opf == NULL || flavors == NULL) + { + perror("Null value in set_op_file\n"); + exit(EXIT_FAILURE); + } + *opf = fopen(opt, "w"); + if (*opf == NULL) { + perror("fopen"); + exit(EXIT_FAILURE); + } + *flavors |= IBS_OP; +} + +void set_fetch_file(char *opt, FILE **fetchf, int *flavors) +{ + if (fetchf == NULL || flavors == NULL) + { + perror("Null value in set_fetch_file\n"); + exit(EXIT_FAILURE); + } + *fetchf = fopen(opt, "w"); + if (*fetchf == NULL) { + perror("fopen"); + exit(EXIT_FAILURE); + } + *flavors |= IBS_FETCH; +} + +void set_working_dir(char *opt) +{ + global_work_dir = opt; +} + +void set_ld_debug_name(char *opt) +{ + ld_debug_out = opt; +} + +void set_global_op_sample_rate(int sample_rate) +{ + int max_sample_rate = 0; + // Check for proper IBS support before we try to read the CPUID information + // about the maximum sample rate. + check_amd_processor(); + check_basic_ibs_support(); + check_ibs_op_support(); + + if (sample_rate < 0x90) + { + fprintf(stderr, "Attempting to set IBS op sample rate too low - %d\n", sample_rate); + fprintf(stderr, "This generation core should not be set below %d\n", 0x90); + exit(EXIT_FAILURE); + } + uint32_t ibs_id = get_deep_ibs_info(); + uint32_t extra_bits = (ibs_id & (1 << 6)) >> 6; + if (!extra_bits) + max_sample_rate = 1<<20; + else + max_sample_rate = 1<<27; + + if (sample_rate >= max_sample_rate) + { + fprintf(stderr, "Attempting to set IBS op sample rate too high - %d\n", sample_rate); + fprintf(stderr, "This generation core can only support up to: %d\n", max_sample_rate-1); + exit(EXIT_FAILURE); + } + op_cnt_max_to_set = sample_rate >> 4; +} + +void set_global_fetch_sample_rate(int sample_rate) +{ + int max_sample_rate = 1<<20; + if (sample_rate < 0) + { + fprintf(stderr, "Attempting to set IBS op sample rate too low - %d\n", sample_rate); + fprintf(stderr, "This generation core should not be set below 0\n"); + exit(EXIT_FAILURE); + } + if (sample_rate >= max_sample_rate) + { + fprintf(stderr, "Attempting to set IBS fetch sample rate too high - %d\n", sample_rate); + fprintf(stderr, "This generation core can only support up to: %d\n", max_sample_rate-1); + exit(EXIT_FAILURE); + } + fetch_cnt_max_to_set = sample_rate >> 4; +} + +void set_global_buffer_size(int in_buffer_size) +{ + if (in_buffer_size < 1) + { + fprintf(stderr, "Attempting to set the buffer size too low - %d\n", in_buffer_size); + exit(EXIT_FAILURE); + } + buffer_size = in_buffer_size * 1024; +} + +void set_global_poll_percent(int in_poll_percent) +{ + if (in_poll_percent < 0 || in_poll_percent > 100) + { + fprintf(stderr, "Error, poll_percent must be between 0 and 100 - tried %d%%\n", in_poll_percent); + exit(EXIT_FAILURE); + } + poll_percent = in_poll_percent; +} + +void set_global_poll_timeout(int in_poll_timeout) +{ + if (in_poll_timeout < 1) + { + fprintf(stderr, "Error, cannot set poll timeout to less than 1ms\n"); + exit(EXIT_FAILURE); + } + poll_timeout = in_poll_timeout; +} + +void parse_args(int argc, char *argv[], FILE **opf, FILE **fetchf, int *flavors) +{ + static struct option longopts[] = + { + {"op_file", required_argument, NULL, 'o'}, + {"fetch_file", required_argument, NULL, 'f'}, + {"library_map", required_argument, NULL, 'l'}, + {"op_sample_rate", required_argument, NULL, 'r'}, + {"fetch_sample_rate", required_argument, NULL, 's'}, + {"buffer_size", required_argument, NULL, 'b'}, + {"poll_percent", required_argument, NULL, 'p'}, + {"poll_timeout", required_argument, NULL, 't'}, + {"working_dir", required_argument, NULL, 'w'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if (opf == NULL || fetchf == NULL) + { + perror("Either opf or fetchf is NULL, can't proceed.\n"); + exit(EXIT_FAILURE); + } + + char c; + while ((c = getopt_long(argc, argv, "+ho:f:l:r:s:b:p:t:w:", longopts, NULL)) != -1) + { + switch (c) { + case 'h': + fprintf(stderr, "This program executes another program and "); + fprintf(stderr, "collects IBS samples during its execution.\n"); + fprintf(stderr, "Usage: ./ibs_monitor [-o op_output] [-f fetch_output] [-w working_directory] [other options below] program_to_run [...]\n"); + fprintf(stderr, "--working_dir (or -w) {dir}:\n"); + fprintf(stderr, " Sets the working direcotry for launching the program to monitor.\n"); + fprintf(stderr, "--op_file (or -o) {filename}:\n"); + fprintf(stderr, " File to which to save IBS op samples\n"); + fprintf(stderr, "--fetch_file (or -f) {filename}:\n"); + fprintf(stderr, " File to which to save fetch samples\n"); + fprintf(stderr, "If you skip either of the file arguments, that type of IBS sampling will be disabled.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "--library_map (or -l) {filename}:\n"); + fprintf(stderr, " Save LD_DEBUG information about dynamic library mappings.. Off by default.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "IBS configuration parameters:\n"); + fprintf(stderr, "--op_sample_rate (or -r) {# ops}:\n"); + fprintf(stderr, " The number of ops between each IBS op sample. Defaults to 256K\n"); + fprintf(stderr, "--fetch_sample_rate (or -s) {# instructions}:\n"); + fprintf(stderr, " The number if instructions between each IBS fetch sample. Defaults to 64K\n"); + fprintf(stderr, "--buffer_size (or -b) {# kB}:\n"); + fprintf(stderr, " The size of the per-core in-kernel IBS storage buffer, in kB. Defaults to 1024 kB\n"); + fprintf(stderr, "--poll_percent (or -p) {%%age}:\n"); + fprintf(stderr, " How full the in-kernel buffer should be before reading it, in %%. Defaults to 75%%\n"); + fprintf(stderr, "--poll_timeout (or -t) {# ms}:\n"); + fprintf(stderr, " How long to wait on the driver before reading a non-full buffer, in ms. Defaults to 1000 ms\n"); + exit(EXIT_SUCCESS); + case 'o': + set_op_file(optarg, opf, flavors); + break; + case 'f': + set_fetch_file(optarg, fetchf, flavors); + break; + case 'l': + set_ld_debug_name(optarg); + break; + case 'r': + set_global_op_sample_rate(atoi(optarg)); + break; + case 's': + set_global_fetch_sample_rate(atoi(optarg)); + break; + case 'b': + set_global_buffer_size(atoi(optarg)); + break; + case 'p': + set_global_poll_percent(atoi(optarg)); + break; + case 't': + set_global_poll_timeout(atoi(optarg)); + break; + case 'w': + set_working_dir(optarg); + break; + case '?': + default: + fprintf(stderr, "Found this bad argument: %s\n", argv[optind]); + break; + } + } +} + +#define print_hdr(opf, fmt, ...) \ +{ \ + char *header_line; \ + int num_bytes = asprintf(&header_line, fmt, __VA_ARGS__); \ + CHECK_ASPRINTF_RET(num_bytes); \ + fwrite(header_line, 1, num_bytes, opf); \ + free(header_line); \ +} + +static void output_machine_setup(FILE *opf, char *argv[]) +{ + uint32_t fam = cpu_family(); + uint32_t model = cpu_model(); + uint32_t stepping = cpu_stepping(); + char *name = cpu_name(); + print_hdr(opf, "AMD Processor Family: 0x%x\n", fam); + print_hdr(opf, "AMD Processor Model: 0x%x\n", model); + print_hdr(opf, "AMD Processor Stepping: 0x%x\n", stepping); + print_hdr(opf, "AMD Processor Name: %s\n", name); + free(name); + + int page_size = getpagesize(); + long int num_phys_pages = get_phys_pages(); + uint64_t total_mem_size = (uint64_t)num_phys_pages * (uint64_t)page_size; + double total_mb = total_mem_size / (1024.*1024.); + double total_gb = total_mem_size / (1024.*1024.*1024.); + if (total_gb > 0) + { + print_hdr(opf, "Memory Size: %.1f GB\n", total_gb); + } + else + { + print_hdr(opf, "Memory Size: %f MB\n", total_mb); + } + + + // Also the name of the OS we ran this on + struct utsname os_info; + uname(&os_info); + print_hdr(opf, "System name: %s\n", os_info.nodename); + print_hdr(opf, "OS: %s %s %s %s\n", os_info.sysname, os_info.release, os_info.version, os_info.machine); + + // Also when we ran things + char timestamp[512]; + time_t cur_time; + time(&cur_time); + strftime(timestamp, 512, "%c", localtime(&cur_time)); + print_hdr(opf, "Timestamp: %s\n", timestamp); + + if (global_work_dir != NULL) + { + print_hdr(opf, "Working directory: %s\n", global_work_dir); + } + else + { + char *cwd; + cwd = getcwd(NULL, 0); + if (cwd != NULL) + { + print_hdr(opf, "Working directory: %s\n", cwd) + free(cwd); + } + else + { + fprintf(stderr, "Unable to find the current working directory.\n"); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + } + + // Also output what command this was + print_hdr(opf, "Command line: %s", ""); + int i = 0; + while (argv[i] != NULL) + { + print_hdr(opf, "%s ", argv[i]); + i++; + } + print_hdr(opf, "%s\n", ""); +} + +static void output_op_header(FILE *opf, char *argv[]) +{ + // Check for proper IBS support + check_amd_processor(); + check_basic_ibs_support(); + check_ibs_op_support(); + + uint32_t fam = cpu_family(); + uint32_t model = cpu_model(); + + // Op header shows that this is an Op file, lists the family, and then + // lists any model-specific support from CPUID_Fn8000_001B_EAX flags. + print_hdr(opf, "IBS Op Sample File%s", "\n"); + + output_machine_setup(opf, argv); + + // Save off the version of the ibs_op_t structure that this application + // will be dumping to disk. This way, old traces can later be read by + // new versions of the decoder. + print_hdr(opf, "IBS Op Structure Version: %u\n", IBS_OP_STRUCT_VERSION); + + // The following bits were only available on Family 10h, Family 12h, + // Family 14h, and Family 15h Models 00h-0Fh + uint32_t brn_resync = 0, misp_return = 0; + if (fam == 0x10 || fam == 0x12 || fam == 0x14 || + (fam == 0x15 && model < 0x10)) + { + brn_resync = 1; + misp_return = 1; + } + + print_hdr(opf, "IbsOpBrnResync: %u\n", brn_resync); + print_hdr(opf, "IbsOpMispReturn: %u\n", misp_return); + + uint32_t ibs_id = get_deep_ibs_info(); + uint32_t branch_target = (ibs_id & (1 << 5)) >> 5; + uint32_t op_cnt_ext = (ibs_id & (1 << 6)) >> 6; + uint32_t rip_invalid_check = (ibs_id & (1 << 7)) >> 7; + uint32_t branch_fuse = (ibs_id & (1 << 8)) >> 8; + uint32_t op_data_4 = (ibs_id & (1 << 10)) >> 10; + print_hdr(opf, "BrnTrgt: %u\n", branch_target); + print_hdr(opf, "OpCntExt: %u\n", op_cnt_ext); + print_hdr(opf, "RipInvalidChk: %u\n", rip_invalid_check); + print_hdr(opf, "OpBrnFuse: %u\n", branch_fuse); + print_hdr(opf, "IbsOpData4: %u\n", op_data_4); + + // Unfortunately, this is based on family/model, not CPUID check. + // This tells us about IBS_OP_DATA[40] + uint32_t microcode = 0; + if ((fam == 0x15 && model >= 0x60) || + fam == 0x17) + microcode = 1; + print_hdr(opf, "Microcode: %u\n", microcode); + + // Family 16h does not have these 2 bits defined. + uint32_t ibs_op_data2_4_5 = 1; + if (fam == 0x14 || fam == 0x16) + ibs_op_data2_4_5 = 0; + print_hdr(opf, "IBSOpData2_4_5: %u\n", ibs_op_data2_4_5); + + // The following are available on Fam 10h, 12h, and 15h Model 00h-0Fh + uint32_t ld_bnk_con = 0, st_to_ld_can = 0; + if (fam <= 0x12 || (fam == 0x15 && model < 0x10)) + { + ld_bnk_con = 1; + st_to_ld_can = 1; + } + // Available on Fam 10h and 12h + uint32_t st_bnk_con = 0; + if (fam <= 0x12) + st_bnk_con = 1; + // Available on Fam 10h, 12h, 15h Model 00h-0Fh, and 16h + uint32_t st_to_ld_fw = 0; + if (fam <= 0x12 || fam == 0x14 || fam == 0x16 || + (fam == 0x15 && model < 0x10)) + { + st_to_ld_fw = 1; + } + print_hdr(opf, "IbsDcLdBnkCon: %u\n", ld_bnk_con); + print_hdr(opf, "IbsDcStBnkCon: %u\n", st_bnk_con); + print_hdr(opf, "IbsDcStToLdFwd: %u\n", st_to_ld_fw); + print_hdr(opf, "IbsDcStToLdCan: %u\n", st_to_ld_can); + + // Available on Fam 15h Models >= 30h, Fam 16h, Fam 17h + uint32_t ibs_data_3_20_31_48_63 = 0; + if (fam >= 0x16 || (fam == 0x15 && model >= 0x30)) + ibs_data_3_20_31_48_63 = 1; + print_hdr(opf, "IbsData3_20_31_48_63: %u\n", ibs_data_3_20_31_48_63); + + // Last line of every header before the binary dump of IBS samples starts + // is 45 equal signs. + print_hdr(opf, "%s\n", ""); + print_hdr(opf, "=============================================%s", "\n"); + return; +} + +static void output_fetch_header(FILE *fetchf, char *argv[]) +{ + // Check for proper IBS support + check_amd_processor(); + check_basic_ibs_support(); + check_ibs_fetch_support(); + + // Op header shows that this is a Fetch file, lists the family, and then + // lists any model-specific support from CPUID_Fn8000_001B_EAX flags. + print_hdr(fetchf, "IBS Fetch Sample File%s", "\n"); + + output_machine_setup(fetchf, argv); + + // Save off the version of the ibs_fetch_t structure that this application + // will be dumping to disk. This way, old traces can later be read by + // new versions of the decoder. + print_hdr(fetchf, "IBS Fetch Structure Version: %u\n", + IBS_FETCH_STRUCT_VERSION); + + uint32_t ibs_id = get_deep_ibs_info(); + uint32_t ibs_fetch_ctl_extd = (ibs_id & (1 << 9)) >> 9; + print_hdr(fetchf, "IbsFetchCtlExtd: %u\n", ibs_fetch_ctl_extd); + + // Last line of every header before the binary dump of IBS samples starts + // is 45 equal signs. + print_hdr(fetchf, "%s\n", ""); + print_hdr(fetchf, "=============================================%s", "\n"); + return; +} + +void output_headers(FILE *opf, FILE* fetchf, int flavors, char *argv[]) +{ + int do_op = flavors | IBS_OP; + if (do_op && opf != NULL) + output_op_header(opf, argv); + + int do_fetch = flavors | IBS_FETCH; + if (do_fetch && fetchf != NULL) + output_fetch_header(fetchf, argv); +} + +char **update_environment(void) +{ + extern char **environ; + uint64_t total_envvar = 0; + + int ld_debug_exists = 0; + while (environ[total_envvar] != NULL) + { + // If LD_DEBUG or LD_DEBUG_OUTPUT are already set, we need to give + // up, because we don't want to overwrite the user's desired + // work. + if (strstr(environ[total_envvar], "LD_DEBUG")) + { + fprintf(stderr, "Found the environment variable based on LD_DEBUG.\n"); + fprintf(stderr, " %s\n", environ[total_envvar]); + ld_debug_exists = 1; + } + total_envvar++; + } + + if (ld_debug_exists) + { + fprintf(stderr, "Because LD_DEBUG is already set in the environment, "); + fprintf(stderr, "we are not changing it.\n"); + return environ; + } + + // Need to add 3. One for LD_DEBUG, another for LD_DEBUG_OUTPUT + // and a final one for the trailing '\0' + char **new_environ = malloc((total_envvar + 3) * sizeof(char *)); + uint64_t i = 0; + for (; i < total_envvar; i++) + { + new_environ[i] = environ[i]; + } + // Set LD_DEBUG + int num_bytes = asprintf(&(new_environ[i]), "LD_DEBUG=files,libs"); + CHECK_ASPRINTF_RET(num_bytes); + i++; + // Set LD_DEBUG_OUTPUT. It will append the child's PID to the name. + num_bytes = asprintf(&(new_environ[i]), "LD_DEBUG_OUTPUT=%s", ld_debug_out); + CHECK_ASPRINTF_RET(num_bytes); + i++; + // Copy the \0 from the orginal environment char**. + new_environ[i] = environ[total_envvar]; + return new_environ; +} + +int main(int argc, char *argv[]) +{ + struct pollfd *fds; + int nopfds = 0; + int nfetchfds = 0; + int flavors = 0; + FILE *opf = NULL; + FILE *fetchf = NULL; + int i; + pid_t cpid; + + set_global_defaults(); + + parse_args(argc, argv, &opf, &fetchf, &flavors); + // Reset argv to real program + argv = &(argv[optind]); + + output_headers(opf, fetchf, flavors, argv); + + poll_size = buffer_size * ((float)poll_percent/100.); + global_buffer = malloc(buffer_size); + + int num_cpus = get_nprocs_conf(); + // Add enough space for fetch and op FDs for every core. + fds = calloc(num_cpus*2, sizeof(struct pollfd)); + enable_ibs_flavors(fds, &nopfds, &nfetchfds, flavors); + + cpid = fork(); + if (cpid == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } + if (cpid == 0) { /* Child process */ + if (global_work_dir != NULL) + { + int err_chk = chdir(global_work_dir); + if (err_chk != 0) + { + fprintf(stderr, "Unable to change working directory to: %s\n", + global_work_dir); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + } + extern char **environ; + char ** environ_to_use; + if (ld_debug_out != NULL) + environ_to_use = update_environment(); + else + environ_to_use = environ; +#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 11) + if (execvpe(argv[0], &argv[0], environ_to_use) == -1) { + fprintf(stderr, "Unable to execute application: %s\n", argv[0]); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +#else + environ = environ_to_use; + if (execvp(argv[0], &argv[0]) == -1) { + fprintf(stderr, "Unable to execute application: %s\n", argv[0]); + fprintf(stderr, " %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +#endif + exit(EXIT_SUCCESS); + } + + reset_ibs_buffers(fds, nopfds + nfetchfds); + + while (!waitpid(cpid, &i, WNOHANG)) + poll_ibs(fds, nopfds, nfetchfds, opf, fetchf); + + flush_ibs_buffers(fds, nopfds, nfetchfds, opf, fetchf); + + disable_ibs(fds, nopfds + nfetchfds); + + // LD_DEBUG_OUTPUT appends the child's PID to the name by default. + // Let's rename it to remove that. + if (ld_debug_out) + { + char *old_name; + int num_bytes = asprintf(&old_name, "%s.%d", ld_debug_out, cpid); + CHECK_ASPRINTF_RET(num_bytes); + int err = rename(old_name, ld_debug_out); + if (err) + { + fprintf(stderr, "Failed to rename LD_DEBUG_OUTPUT file to "); + fprintf(stderr, "its correct final name.\n"); + fprintf(stderr, " %s\n", strerror(err)); + } + } + + if (opf != NULL || fetchf != NULL) + { + printf("\nIBS sampling statistics:\n"); + printf("op_samples,op_samples_lost,fetch_samples,fetch_samples_lost\n"); + printf("%lu,%lu,%lu,%lu\n", n_op_samples, n_lost_op_samples, + n_fetch_samples, n_lost_fetch_samples); + } + + free(fds); + exit(EXIT_SUCCESS); +} + +static int fill_out_online_cores(int num_cpus, int num_online_cpus, + char *cpu_list) +{ + // Skip opening the file if all CPUs are online. + // Older Linux installs this will always be true and the + // 'online' file won't exist. + if (num_cpus == num_online_cpus) + { + for (int i = 0; i < num_cpus; i++) + { + cpu_list[i] = 1; + } + return num_online_cpus; + } + + FILE *online_fp = fopen("/sys/devices/system/cpu/online", "r"); + if (online_fp == NULL) + { + fprintf(stderr, "Could not open /sys/devices/system/cpu/online\n"); + exit(EXIT_FAILURE); + } + + char *online_cpus = NULL; + size_t len = 0; + ssize_t line_len = getline(&online_cpus, &len, online_fp); + if (line_len == -1 || len == 0) + { + fprintf(stderr, "Failed to read the number of online CPUs\n"); + exit(EXIT_FAILURE); + } + + int i = 0; + int previous_cpu = -1; + int num_online_seen = 0; + while (online_cpus[i] != '\0' && online_cpus[i] != '\n') + { + if (online_cpus[i] == ',') + { + i++; + continue; + } + else if (online_cpus[i] == '-') + { + // This only fills in the missing ones. The end-points + // are filled in during the normal numbers. + int next_cpu = atoi(&(online_cpus[i+1])); + for (int j = previous_cpu + 1; j < next_cpu; j++) + { + num_online_seen++; + cpu_list[j] = 1; + } + while(isdigit(online_cpus[i])) + i++; + previous_cpu = next_cpu; + } + else + { + int cpu_num = atoi(&(online_cpus[i])); + if (cpu_num < num_cpus) + { + num_online_seen++; + cpu_list[cpu_num] = 1; + previous_cpu = cpu_num; + } + else + break; + } + i++; + if (num_online_seen >= num_online_cpus) + break; + } + fclose(online_fp); + return previous_cpu; +} + +/** + * enable_ibs_flavors - turn on IBS where possible + * @fds: (output) file descriptors and events of interest for poll + * @nopfds: (output) number of op file descriptors successfully set up + * @nfetchfds: (output) number of fetch file descriptors successfully set up + * @flavors: IBS_OP, IBS_FETCH, or IBS_BOTH + */ +void enable_ibs_flavors(struct pollfd *fds, int *nopfds, int *nfetchfds, + int flavors) +{ + char filename [64]; + int count = 0; + int cpu; + + n_op_samples = 0; + n_fetch_samples = 0; + n_lost_op_samples = 0; + n_lost_fetch_samples = 0; + + int num_cpus = get_nprocs_conf(); + char *cpu_list = calloc(num_cpus, sizeof(char)); + int num_online_cpus = get_nprocs(); + fill_out_online_cores(num_cpus, num_online_cpus, cpu_list); + + *nopfds = 0; + if (flavors & IBS_OP) { + for (cpu = 0; cpu < num_cpus; cpu++) { + if (!cpu_list[cpu]) + continue; + + sprintf(filename, "/dev/cpu/%d/ibs/op", cpu); + fds[count].fd = open(filename, O_RDONLY | O_NONBLOCK); + if (fds[count].fd < 0) { + fprintf(stderr, "Could not open %s\n", filename); + continue; + } + + ioctl(fds[count].fd, SET_BUFFER_SIZE, buffer_size); + ioctl(fds[count].fd, SET_POLL_SIZE, + poll_size / sizeof(ibs_op_t)); + ioctl(fds[count].fd, SET_MAX_CNT, op_cnt_max_to_set); + if (ioctl(fds[count].fd, IBS_ENABLE)) { + fprintf(stderr, "IBS op enable failed on cpu %d\n", + cpu); + continue; + } + + fds[count].events = POLLIN | POLLRDNORM; + (*nopfds)++; + count++; + } + } + + *nfetchfds = 0; + if (flavors & IBS_FETCH) { + for (cpu = 0; cpu < num_cpus; cpu++) { + if (!cpu_list[cpu]) + continue; + + sprintf(filename, "/dev/cpu/%d/ibs/fetch", cpu); + fds[count].fd = open(filename, O_RDONLY | O_NONBLOCK); + if (fds[count].fd < 0) { + fprintf(stderr, "Could not open %s\n", filename); + continue; + } + + ioctl(fds[count].fd, SET_BUFFER_SIZE, buffer_size); + ioctl(fds[count].fd, SET_POLL_SIZE, + poll_size / sizeof(ibs_fetch_t)); + ioctl(fds[count].fd, SET_MAX_CNT, fetch_cnt_max_to_set); + if (ioctl(fds[count].fd, IBS_ENABLE)) { + fprintf(stderr, "IBS fetch enable failed on cpu %d\n", + cpu); + continue; + } + + fds[count].events = POLLIN | POLLRDNORM; + (*nfetchfds)++; + count++; + } + } + + if (cpu_list) + free(cpu_list); +} + +/** + * reset_ibs_buffers + */ +void reset_ibs_buffers(const struct pollfd *fds, int nfds) +{ + for (int i = 0; i < nfds; i++) + ioctl(fds[i].fd, RESET_BUFFER); +} + +static inline void read_and_write_op_data(int fd, FILE *fp) +{ + int tmp = 0; + int num_items = 0; + + tmp = read(fd, global_buffer, buffer_size); + if (tmp <= 0) + return; + num_items = tmp / sizeof(ibs_op_t); + + if (fp != NULL) + { + tmp = fwrite(global_buffer, sizeof(ibs_op_t), num_items, fp); + if (tmp < num_items) + fprintf(stderr, "Failed to write %d samples\n", + num_items - tmp); + } + + n_op_samples += num_items; + n_lost_op_samples += ioctl(fd, GET_LOST); +} + +static inline void read_and_write_fetch_data(int fd, FILE *fp) +{ + int tmp; + int num_items = 0; + + tmp = read(fd, global_buffer, buffer_size); + if (tmp <= 0) + return; + num_items = tmp / sizeof(ibs_fetch_t); + + if (fp != NULL) + { + tmp = fwrite(global_buffer, sizeof(ibs_fetch_t), num_items, fp); + if (tmp < num_items) + fprintf(stderr, "Failed to write %d samples\n", + num_items - tmp); + } + + n_fetch_samples += num_items; + n_lost_fetch_samples += ioctl(fd, GET_LOST); +} + +/** + * poll_ibs - collect data and write it to some files + */ +void poll_ibs(struct pollfd *fds, int nopfds, int nfetchfds, FILE *opf, + FILE *fetchf) +{ + int tmp; + int i; + + /* Wait up to POLL_TIMEOUT if nothing is ready */ + tmp = poll(fds, nopfds + nfetchfds, poll_timeout); + if (tmp == -1) { + perror("poll()"); + exit(EXIT_FAILURE); + } else if (tmp == 0) { + return; + } + + /* Something is ready */ + for (i = 0; i < nopfds; i++) { + if (fds[i].revents) + read_and_write_op_data(fds[i].fd, opf); + } + for (i = nopfds; i < (nopfds + nfetchfds); i++) { + if (fds[i].revents) + read_and_write_fetch_data(fds[i].fd, fetchf); + } +} + +/** + * flush_ibs_buffers - read all data from all fds + */ +void flush_ibs_buffers(const struct pollfd *fds, int nopfds, int nfetchfds, + FILE *opf, FILE *fetchf) +{ + int i; + + for (i = 0; i < nopfds; i++) + read_and_write_op_data(fds[i].fd, opf); + for (i = nopfds; i < (nopfds + nfetchfds); i++) + read_and_write_fetch_data(fds[i].fd, fetchf); +} + +/** + * disable_ibs + */ +void disable_ibs(const struct pollfd *fds, int nfds) +{ + for (int i = 0; i < nfds; i++) { + ioctl(fds[i].fd, IBS_DISABLE); + close(fds[i].fd); + } +} + +``` + +`tools/ibs_monitor/ibs_monitor.h`: + +```h +/* + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This file is distributed under the BSD license described in tools/LICENSE + */ + +#ifndef IBS_MONITOR_H +#define IBS_MONITOR_H + +#define CHECK_ASPRINTF_RET(num_bytes) \ +{ \ + if (num_bytes <= 0) \ + { \ + fprintf(stderr, "asprintf in %s:%d failed\n", __FILE__, __LINE__); \ + exit(-1); \ + }\ +} + +// Defines to help the rest of the program differentiate op and fetch samples. +#define IBS_OP 1 +#define IBS_FETCH 2 +#define IBS_BOTH (IBS_OP | IBS_FETCH) + +// IBS sampling sizes for IBS op and IBS fetch devices. +// If your program has too much slowdown from IBS, you should increase these +// If you are not getting enough samples to observe interesting things in +// your application, decrease these. +// Op sampling interval +#define OP_MAX_CNT 0x4000 // This will sample roughly 1 out of ~262,000 ops + // 16 * 0x4000 (16,384) = 262,144 +// Fetch sampling interval +#define FETCH_MAX_CNT 0x1000 // This will sample roughly 1/64k instructions + // 16 * 0x1000 (4096) = 65,536 + +// The buffer size (in bytes) that the driver will use to hold the IBS samples. +// This is a *per core* value. +// The larger this is, the less likely a sample will be stomped before the +// user-level application can bulk read the data out. However, larger buffers +// obviously take up more kernel memory. +#define BUFFER_SIZE_B (1 << 20) // 1 MB by default. Increase this if you + // keep getting lost samples. + +// Max portion of the IBS driver's buffer that should be full before it wakes +// up the process after a poll(), and says "you should read this data now." +#define POLL_SIZE_PERCENT 75 +// Max time (in ms) to wait (poll()) on the IBS driver befor reading out +// data. This prevents needless overhead from the monitor application spinning +// on the read() command. +#define POLL_TIMEOUT 1000 + +// The IBS Monitor application uses a number of global variables to hold things +// like the file handler outputs, the op and fetch sample rates, and the size +// of the kernel buffer that it will request from the IBS driver. +// This function sets them all to their default values (which are defined +// by the macros above in this .h file +void set_global_defaults(void); + +// The following functions will set the global variables, and include some +// limit and error checking. + +// Arg 1: Input - Filename string. +// Arg 2: Output - Pointer to the FILE* to initialize with this function. +// Arg 3: Output - Pointer to the variable that will have IBS_OP/FETCH +// Added to it. +void set_op_file(char *opt, FILE **opf, int *flavors); +void set_fetch_file(char *opt, FILE **fetchf, int *flavors); +// The sample rates here are the actual sample rates you want. However, the +// bottom 4 bits of what you put in will be randomized in the driver. +void set_global_op_sample_rate(int sample_rate); +void set_global_fetch_sample_rate(int sample_rate); +// Buffer size in KB +void set_global_buffer_size(int in_buffer_size); +void set_global_poll_percent(int in_poll_percent); +// Timeout in ms +void set_global_poll_timeout(int in_poll_timeout); + + +// Call this early in the application in order to parse the command line +// arguments and fill out global variables, open outputs, etc. etc. +void parse_args(int argc, char *argv[], FILE **opf, FILE **fetchf, + int *flavors); + +/** + * enable_ibs_flavors - turn on IBS where possible + * @fds: (output) file descriptors and events of interest for poll + * @nopfds: (output) number of op file descriptors successfully set up + * @nfetchfds: (output) number of fetch file descriptors successfully set up + * @flavors: IBS_OP, IBS_FETCH, or IBS_BOTH + */ +void enable_ibs_flavors(struct pollfd *fds, int *nopfds, int *nfetchfds, + int flavors); +void reset_ibs_buffers(const struct pollfd *fds, int nfds); +void poll_ibs(struct pollfd *fds, int nopfds, int nfetchfds, FILE *opf, + FILE *fetchf); +void flush_ibs_buffers(const struct pollfd *fds, int nopfds, int nfetchfds, + FILE *opf, FILE *fetchf); +void disable_ibs(const struct pollfd *fds, int nfds); + +#endif /* IBS_MONITOR_H */ + +``` + +`tools/ibs_run_and_annotate/Makefile`: + +``` +# Copyright (c) 2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See tools/LICENSE for licensing details. + +all: + +clean: + rm -f $(THIS_TOOL_DIR)*.csv $(THIS_TOOL_DIR)*.dat $(THIS_TOOL_DIR)*.txt + +``` + +`tools/ibs_run_and_annotate/ibs_run_and_annotate`: + +``` +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2017-2018 Advanced Micro Devices, Inc. + +# This file is distributed under the BSD license described in tools/LICENSE + +"""IBS Run and Annotate Tools +This script will run an application and gather IBS op and/or fetch samples. +It will then take those samples and annotate them with information about +which instructions (and lines of code) were sampled. + +It does this by first asking the IBS monitor application (found in +tools/ibs_monitor/) to save off dynamic library linkage when the +application-under-test is started. This is done by using glibc's +LD_DEBUG mechanism. This allows us to understand the base address and range +of any dynamic libraries; IBS samples with an instruction pointer into that +region of memory were from code in that dynamically linked library. + +This tool will then automatically perform this mapping of IBS samples to +the x86 instruction (and line of code) that they represent, based on the +IBS samples' instruction pointer. This is done with the 'objdump' utilty. +Mapping IBS samples to instructions this way should almost always works; +mapping IBS samples to lines of code will require the target application +be built with with debug symbols. +""" + +from __future__ import print_function +from multiprocessing import cpu_count, Process +from subprocess import Popen, PIPE +from operator import itemgetter +from bisect import bisect +from time import time +from tempfile import mkstemp +from shutil import copyfileobj +from distutils.spawn import find_executable +from textwrap import wrap +import csv +import os +import sys +import argparse +from joblib import Parallel, delayed + +# RIP_DICT is global dictionary. However +# joblib.Parallel will fork #num_cores processes +# which will each have its own copy of global RIP_DICT +# We can't pass this into the job, because then the cache will +# never actually be filled. +RIP_DICT = {} +VERBOSE = 0 +def vprint(msg): + """Print to the screen if running this script in VERBOSE mode""" + if VERBOSE: + print(msg) + +# Instruction lookup takes in a list of RIPs to look up. +# This assumes that the list of library base+limits have already been put +# into the global lib_base and lib_size Maps. +def inst_lookup(in_file, start_byte, end_byte, pid, poi, fetch=False): + """Parallel worker thread that takes an input line from an IBS CSV file, + looks up the instruction pointer for each sample, and tries to find where + in the binary or libraries it was located (and which instruction it was). + + It will ignore samples not from the target process, any samples from kernel + mode, and any fetch samples without valid physical addrsses. + + It uses objdump to obtain this information. Because calling a separate + process like objdump can be very slow, this function attempts to cache + any lookups it does, and actually prefetches onto future instructions + in an attempt to proactively fill up that cache. + + Keyword arguments: + in_file -- The name of the input file we will be reading with this + parallel worker thread. + start_byte -- Within the input file, where should this worker start? + end_byte -- Within this input file, where will this worker stop? + pid -- The process ID of the process that is under analysis. + poi -- Path to the program of interest + fetch -- Are these Fetch samples? (Default: False) + """ + + input_csv = open(in_file, "r") + input_csv.seek(start_byte,0) + + temp_file = mkstemp(suffix='.csv', prefix='ibs_merge_', text=True) + fout_name = temp_file[1] + fout = open(fout_name, 'w+') + csv_row_num = 0 + total_bytes = 0 + print_me = [] + for line in input_csv: + total_bytes += len(line) + if (start_byte + total_bytes) > end_byte: + break + + line_csv = line.split(",") + + # The first line is the header. Ignore that. + if line_csv[0] == "TSC": + continue + + csv_row_num += 1 + + # We must strip out any kernel-mode IBS samples, as well as any samples + # not from the PID we care about. For fetch addresses, if it doesn't + # have valid physical instruction address, then we do not care. + # Col 3 is PID + # Col 4 is kernel mode + # Col 5 is Phy_valid in fetch mode + if (int(line_csv[3]) == int(pid) and + int(line_csv[4]) == 0 and + ((not fetch) or (int(line_csv[5]) == 1))): + if fetch: + rip = int(line_csv[6], 16) + else: + rip = int(line_csv[5], 16) + else: + continue + + # First, check to see if we have decoded this RIP before. If so, just + # pull it from the cache. This will make the decoding run faster. + real_line = line + if rip in RIP_DICT: + data_to_output = RIP_DICT[rip] + else: + # Check to see if this RIP was from a dynamically linked library. + lib_idx = bisect(lib_base, rip) - 1 + if lib_idx < 0: + # Was in the program itself. + base_offset = 0 + inst_offset = rip + obj_name = poi + elif rip < lib_base[lib_idx] + lib_size[lib_idx]: + # Was in a library. + base_offset = lib_base[lib_idx] + inst_offset = (rip - base_offset) + obj_name = libs[lib_idx][2] + else: + # No idea where it came from + if VERBOSE: + sys.stderr.write('RIP {} not found.\n'.format(hex(rip))) + continue + + # We use objdump to decode these instructions. + # Set width to 15, which is the longest legal AMD64 instruction. + # We start by trying to decode the actual RIP (or decode the offset + # of this RIP in the target library). We then try to decode another + # kB of instructions afterwards, to prefetch into our cache. + # We have a cache of decodes because calling objdump repeatedly for + # every instruction can lead to huge slowdowns. + cmd = ['objdump', '-dl', '--insn-width=15 ' + , '--start-address=' + hex(inst_offset) \ + , '--stop-address=' + hex(inst_offset + 0x3ff) \ + , obj_name \ + ] + proc = Popen(cmd, stdout=PIPE, universal_newlines=True) + out = proc.communicate()[0] + inst_idx_beg = out.find(hex(inst_offset)[2:] + ':') + # we tried objdump binary yet no inst found + if inst_idx_beg < 0: + if VERBOSE: + sys.stderr.write("RIP in dynsym or rela.dyn " + hex(rip) + "\n") + continue + inst_idx_end = out.find('\n', inst_idx_beg) + 1 + # dumping the above line that has source code info + source_idx_end = out.rfind('\n', 0, inst_idx_beg) + source_idx_beg = out.rfind('\n', 0, source_idx_end) + 1 + source_info = out[source_idx_beg:source_idx_end-1] + inst = out[source_idx_end+1:inst_idx_end].lstrip().split('\t') + inst[0] = "0x" + inst[0].rstrip(':') + inst[1] = "0x" + inst[1].replace(' ', '') + if len(inst) > 2: + inst[2] = inst[2].lstrip().rstrip().replace(',', ' ') + inst[2] += ',\n' + else: + inst.append(',\n') + res = source_info + ',' + ','.join(inst) + RIP_DICT[rip] = res + data_to_output = res + # Everything down here is to add as many lines as possible into + # the translation cache. So we try to go through as many "extra" + # translations as possible. This is why we set the end address + # of the objdump as 0x3ff after our initial translation. + following_lines = out[inst_idx_end:].splitlines() + for line in following_lines: + presumable_addr = line[:line.find(':')] + if line.find("(bad)") != -1: + # Some of the future translations of objdump will be bad + # because objdump will assume that every future instruction + # is forwarded decoded from our start address. If this + # future instruction is not aligned with the initial + # address, then the translation can come back as (bad). + # Don't add that into the cache -- this is useless work. + # We shouldn't see these unaligned addresses in the future. + break + try: + rip_in_block = base_offset + int(presumable_addr, 16) + inst = line.split('\t') + inst[0] = "0x" + inst[0].lstrip().rstrip(':') + inst[1] = "0x" + inst[1].replace(' ', '') + if len(inst) > 2: + inst[2] = inst[2].lstrip().rstrip().replace(',', ' ') + inst[2] += ',\n' + else: + # We should not see any lines with a "blank" decoding. + # This normally happens when the instruction is longer + # than the objdump insn-width. However, we should have + # set that to the maximum x86 instruction width. + # This might be a bad instruction, so don't cache it. + break + res = source_info + ',' + ','.join(inst) + RIP_DICT[rip_in_block] = res + except ValueError: + source_info = line[:-1] + print_me.append(real_line.strip() + data_to_output.strip() + "\n") + fout.write(''.join(print_me)) + fout.close() + input_csv.close() + return fout_name + + +# dumpling the lib infos: path, base, size +def read_ld_debug(ld_debug_file): + """This function reads information from a text file that was output by ld.so + based on the LD_DEBUG environment variable. This will allow our tool to + understand the path to any included libraries, the base address used during + this run of the application, and the size in memory of that loaded library. + + Keyword arguments: + ld_debug_file -- path to the text file with LD_DEBUG outputs. + """ + lib_list = [] + with open(ld_debug_file, 'r') as fin: + pid_from_file = fin.readline().split(':')[0].strip() + lines = fin.readlines() + for i, line in enumerate(lines): + if 'base:' in line: + tmp = line.split() + lib_list.append((int(tmp[4], 16), int(tmp[6], 16), + lines[i - 3].split('=')[1].strip())) + # sort the libs based on their base addr + lib_list.sort(key=itemgetter(0)) + vprint('Finished dumping library information.') + return lib_list, pid_from_file + +# Dump the CSV file into chunks of 4k rows so that we can process them +# in parallel. This generator will yield a [list of row_strings] +def dump_csv(samples_filename): + """Open a CSV file and create lists out of the lines. + This will yield multiple lists, each of which will have up to 4k lines + from the file in it. This allows us to process these lines in parallel. + """ + row_buf = [] + row_count = 0 + + start_byte = 0 + end_byte = 0 + with open(samples_filename, 'r') as fin: + for row in fin: + end_byte += len(row) + row_count = row_count + 1 + if row_count % 0x4000 == 0: + yield start_byte, end_byte + start_byte = end_byte + row_count = 0 + if row_count != 0: + yield start_byte, end_byte + +def write_csv(all_temp_files, in_csv_file, out_file): + """Write a final CSV file based on a large number of annotated IBS CSV + files that were created in parallel. This will also create the new + annotated header. + """ + # We add four columns to the CSV's header + with open(in_csv_file, 'r') as fin: + first_line = fin.readline().split(',') + fin.close() + with open(out_file, 'w+') as fout: + fout_csv = csv.writer(fout, lineterminator='\n') + first_line.pop() # kill the trailing newline + first_line.extend(['Source_Line', 'Binary_Offset', 'Opcode', 'Instruction']) + fout_csv.writerow(first_line) + for cat_file in all_temp_files: + copyfileobj(open(cat_file, 'r'), fout) + os.remove(cat_file) + fout.close() + +def parse_and_run_ibs(): + script = sys.argv[0] + """Parse the arguments for this script.""" + parser = argparse.ArgumentParser(description='\n'.join(wrap('This tool ' + 'will run an application and gather IBS ' + 'op and/or fetch samples from it. It will ' + 'then take those samples and annotate ' + 'them with information about which ' + 'instructions (and lines of code) were ' + 'sampled.',80)) + '\n\n' + + '\n'.join(wrap('You should put a " -- " ' + 'between the arguments to this tool and ' + 'the command to run if it will have its ' + 'own options. For example:',80)) + "\n" + + str(script) + ' -o -- ls -alh\n\n' + + '\n'.join(wrap('If you wish to redirect ' + 'the output of your target command (and ' + 'not this tool), place the command to run ' + 'in double quotes and use your /bin/sh ' + 'shell\'s syntax to do the redirect. For ' + 'example:',80)) + '\n' + str(script) + + ' -o -- "ls -alh > output 2>&1"\n\n', + formatter_class= + argparse.RawDescriptionHelpFormatter) + parser.add_argument('-l', '--lookup', action='store_true', + dest='lookup_only', + help='Perform instruction lookup/parsing only. '\ + 'Skip running the application and use existing IBS '\ + 'traces instead. Will look for op_csv_name and '\ + 'fetch_csv_name as inputs.') + parser.add_argument('-o', '--op_sample', action='store', + dest='op_sample_rate', const='262144', default='0', + nargs='?', + help='Gather/annotate IBS op samples. Without passing '\ + 'a number to this argument, we will default to '\ + 'sampling every 256K ops. Optionally pass a number '\ + 'to change the op sampling rate.') + parser.add_argument('-f', '--fetch_sample', action='store', + dest='fetch_sample_rate', const='65536', default='0', + nargs='?', + help='Gather/annotate IBS fetch samples. Without '\ + 'passing a number to this argument, we will default '\ + 'to sampling every 64K fetches. Optionally pass a ' + 'number to change fetch sampling rate.') + parser.add_argument('-t', '--temp_dir', + default=os.path.abspath(os.getcwd()), + help='Directory used to store the temporary IBS '\ + 'trace files. This includes the raw IBS data '\ + 'output from the monitor and the CSV files output '\ + 'from the IBS decoder. (default: current working dir)') + parser.add_argument('--op_sample_file', default='ibs_op.dat', + help='File used to store IBS op traces from the '\ + 'monitor. This file will be stored into the TEMP_DIR '\ + 'directory. (default: %(default)s)') + parser.add_argument('--fetch_sample_file', default='ibs_fetch.dat', + help='File used to store IBS fetch traces from the '\ + 'monitor. This file will be stored into the TEMP_DIR '\ + 'directory. (default: %(default)s)') + parser.add_argument('--op_csv', default='ibs_op.csv', + help='File used to hold decoded IBS op traces. '\ + 'Will create/look for this file in the TEMP_DIR '\ + 'directory. (default: %(default)s)') + parser.add_argument('--fetch_csv', default='ibs_fetch.csv', + help='File used to hold decoded IBS fetch traces. '\ + 'Will create/look for this file in the TEMP_DIR '\ + 'directory. (default: %(default)s)') + parser.add_argument('--ld_debug_file', default='ld_debug.txt', + help='File to hold the LD_DEBUG information about '\ + 'the application, so that we can understand its '\ + 'dynamic library locations. Will create/look for '\ + 'this file in the TEMP_DIR directory.'\ + ' (default: %(default)s)') + parser.add_argument('-d', '--out_dir', + default=os.path.abspath(os.getcwd()), + help='Directory used to store the tool output.'\ + ' (default: current working dir)') + parser.add_argument('--op_output', default='ibs_annotated_op.csv', + help='File used to hold the annotated IBS op traces. '\ + 'Will be created in the OUT_DIR directory.'\ + ' (default: %(default)s)') + parser.add_argument('--fetch_output', default='ibs_annotated_fetch.csv', + help='File used to hold the annotated IBS fetch '\ + 'traces. Will be created in the OUT_DIR directory.'\ + ' (default: %(default)s)') + parser.add_argument('-w', '--working_dir', + default=os.path.abspath(os.getcwd()), + help='Set the working directory for the program '\ + 'under test. (default: current working dir)') + parser.add_argument('--timer', action='store_true', + help='Time different sections of this runscript.') + parser.add_argument('command', nargs="*", + help='Command to run program under test, or pointer '\ + 'to the binary that was used when gathering the CSV '\ + 'files. This will be combined with WORKING_DIR if '\ + 'the command is not an absolute path. If WORKING_DIR '\ + 'is not set, or the binary is not found in that '\ + 'directory, this tool will check the PATH '\ + 'environment variable.') + args = parser.parse_args() + + if not (args.op_sample_rate != '0' or args.fetch_sample_rate != '0'): + print("Error! Neither IBS op nor IBS fetch samples were requested.") + parser.error('Select -o and/or -f to work on op and/or fetch samples') + + # After parsing all the arguments, set up directory and file paths + ibs_tools_dir = os.path.join(os.path.dirname(__file__), "..") + op_sample_file = os.path.join(args.temp_dir, args.op_sample_file) + fetch_sample_file = os.path.join(args.temp_dir, args.fetch_sample_file) + op_csv_file = os.path.join(args.temp_dir, args.op_csv) + fetch_csv_file = os.path.join(args.temp_dir, args.fetch_csv) + ld_debug_file = os.path.join(args.temp_dir, args.ld_debug_file) + op_out_file = os.path.join(args.out_dir, args.op_output) + fetch_out_file = os.path.join(args.out_dir, args.fetch_output) + + # The final thing parsed out is the application to run (and the rest + # of *its* arguments, optionally). Let's find the application and make + # a variable that holds its path. If the command line passed in is not + # an absolute path (or something like ~/foo.exe), then we try to find it + # in the directory pointed to by the --working_dir argument. If it's + # also not there, we fall back to checking the PATH environment vars. + prog = args.command + + # If prog was passed in with double quotes, then prog[0] is a string + if len(prog) == 1: + # split the string on spaces so we can see what the app actually was + prog_unquote = prog[0].split(' ') + prog = prog_unquote + + prog[0] = os.path.expanduser(prog[0]) + prog[0] = os.path.expandvars(prog[0]) + if os.path.isabs(prog[0]): + bench_file = prog[0] + else: + bench_file = find_executable(prog[0]) + if bench_file == None: + bench_file = os.path.join(args.working_dir, prog[0]) + + if not os.path.isfile(bench_file): + print("Error! Cannot find the desired program: " + prog[0]) + sys.exit("Will not be able to properly gather/decode the IBS trace data.") + + if not args.lookup_only: + # Set up the command line to run the IBS monitoring application + ibs_monitor_bin = os.path.join(ibs_tools_dir, 'ibs_monitor/ibs_monitor') + if not os.path.isfile(ibs_monitor_bin): + print("Error! The IBS monitor application was not found.") + print("Have you run 'make' in the tools directory?") + print(" " + str(ibs_tools_dir)) + sys.exit("Could not run requested commands.") + ibs_monitor_cmd = [ibs_monitor_bin, '-l', ld_debug_file] + if args.working_dir: + ibs_monitor_cmd += ['-w', args.working_dir] + if args.op_sample_rate != '0': + ibs_monitor_cmd += ['-o', op_sample_file] + ibs_monitor_cmd += ['-r', args.op_sample_rate] + if args.fetch_sample_rate != '0': + ibs_monitor_cmd += ['-f', fetch_sample_file] + ibs_monitor_cmd += ['-s', args.fetch_sample_rate] + ibs_monitor_cmd += prog + # Set up the command to run the IBS decoding application + ibs_decoder_bin = os.path.join(ibs_tools_dir, 'ibs_decoder/ibs_decoder') + if not os.path.isfile(ibs_monitor_bin): + print("Error! The IBS decoder application was not found.") + print("Have you run 'make' in the tools directory?") + print(" " + str(ibs_tools_dir)) + sys.exit("Could not run requested commands.") + ibs_decoder_cmd = [ibs_decoder_bin] + if args.op_sample_rate != '0': + ibs_decoder_cmd += ['-i', op_sample_file] + ibs_decoder_cmd += ['-o', op_csv_file] + if args.fetch_sample_rate != '0': + ibs_decoder_cmd += ['-f', fetch_sample_file] + ibs_decoder_cmd += ['-g', fetch_csv_file] + + # run IBS monitor and decoder with program of interest + print('IBS Tools Running Command: ' + ' '.join(prog)) + print(' From directory: ' + str(args.working_dir)) + try: + ret_val = Popen(' '.join(ibs_monitor_cmd), shell=True).wait() + if ret_val != 0: + raise ValueError('Failed to run the IBS monitor!') + ret_val = Popen(' '.join(ibs_decoder_cmd), shell=True).wait() + if ret_val != 0: + raise ValueError('Failed to run the IBS decoder!') + except OSError: + parser.error('IBS Tools not found!') + except ValueError as err: + sys.exit(str(err.args[0])) + else: + # check if sample csv files exist + if args.op_sample_rate != '0' and not os.path.exists(op_csv_file): + parser.error('IBS op samples specified, but CSV file ' + + op_csv_file + ' not found!') + if args.fetch_sample_rate != '0' and not os.path.exists(fetch_csv_file): + parser.error('IBS fetch samples specified, but CSV file ' + + fetch_csv_file + ' not found!') + if not os.path.exists(ld_debug_file): + parser.error('IBS LD_DEBUG info file (' + + ld_debug_file + ') does not exist!') + return (bench_file, args.op_sample_rate, args.fetch_sample_rate, + op_csv_file, fetch_csv_file, ld_debug_file, op_out_file, + fetch_out_file, args.timer) + +def main(): + """Main function for this application""" + app_start_time = time() + + # poi: program of interest + (poi, dump_op_rate, dump_ft_rate, op_csv_fn, fetch_csv_fn, ld_debug_fn, + op_out_fn, fetch_out_fn, timer) = parse_and_run_ibs() + + annotate_start_time = time() + + # Start by gathering information about the libraries that were dynamically + # linked when the application ran. + # Putting these into globals so that we don't have to push them through IPC + # to the parallel processes we will launch with joblib. + global libs + global lib_base + global lib_size + libs, pid_to_use = read_ld_debug(ld_debug_fn) + vprint('Dumping info for process {}'.format(pid_to_use)) + lib_base = map(itemgetter(0), libs) + lib_size = map(itemgetter(1), libs) + + # Next, split off parallel jobs to look up the IBS fetch or op samples + # within the application or its shared libraries. + num_cores = cpu_count() + vprint('Running IBS lookups in parallel with {} jobs'.format(num_cores)) + + dump_and_lookup_start_time = time() + + if dump_op_rate != '0': + print("Beginning looking up instructions for IBS op samples...") + op_lookup_start_time = time() + op_res = Parallel(n_jobs=num_cores-1, pre_dispatch=('1.5*n_jobs'))( + delayed(inst_lookup)(op_csv_fn, start_byte, end_byte, pid_to_use, poi, False) + for start_byte, end_byte in dump_csv(op_csv_fn)) + op_lookup_done_time = time() + print("Done with IBS op lookups.") + if dump_ft_rate != '0': + proc = None + if dump_op_rate != '0': + # pipeline the final file write with fetch sample processing + proc = Process(target=write_csv, + args=(op_res, op_csv_fn, op_out_fn)) + proc.start() + print("Beginning looking up instructions for IBS fetch samples...") + fetch_lookup_start_time = time() + ft_res = Parallel(n_jobs=num_cores-1, pre_dispatch=('1.5*n_jobs'))( + delayed(inst_lookup)(fetch_csv_fn, start_byte, end_byte, pid_to_use, poi, True) + for start_byte, end_byte in dump_csv(fetch_csv_fn)) + fetch_lookup_done_time = time() + print("Done with IBS fetch lookups.") + write_csv(ft_res, fetch_csv_fn, fetch_out_fn) + fetch_write_done_time = time() + if proc is not None: + proc.join() + elif dump_op_rate != '0': + write_csv(op_res, op_csv_fn, op_out_fn) + op_write_done_time = time() + + # If the user has requested timing information, print out all of the + # timers from across the application. + if timer: + end_time = time() + total_time = end_time - app_start_time + app_runtime = annotate_start_time - app_start_time + library_parse = dump_and_lookup_start_time - annotate_start_time + lookup_and_write = end_time - dump_and_lookup_start_time + print("IBS run and annotate time: %.3f s" % total_time) + print("\tApplication time: %.3f s" % app_runtime) + print("\tParsing libraries time: %.3f s" % library_parse) + print("\tAnnotate and CSV write time: %.3f s" % lookup_and_write) + if dump_op_rate != '0': + op_lookup = op_lookup_done_time - op_lookup_start_time + print("\t\tIBS Op lookup time: %.3f s" % op_lookup) + # The op write timer only exists if we are not doing fetch lookups. + # Otherwise, the fetch lookups and writing take place in parallel. + if dump_ft_rate == '0': + op_write = op_write_done_time - op_lookup_done_time + print("\t\tIBS Op CSV write time: %.3f s" % op_write) + if dump_ft_rate != '0': + fetch_lookup = fetch_lookup_done_time - fetch_lookup_start_time + fetch_write = fetch_write_done_time - fetch_lookup_done_time + if dump_op_rate != '0': + possible_op_write = (lookup_and_write - fetch_lookup - + fetch_write - op_lookup) + if possible_op_write > 0.5: + print("\t\tIBS Op CSV write time: %.3f s" % + possible_op_write) + else: + print("\t\tIBS Op CSV write time: 0.000 s (fully pipelined)") + print("\t\tIBS Fetch lookup time: %.3f s" % fetch_lookup) + print("\t\tIBS Fetch CSV write time: %.3f s" % fetch_write) + +if __name__ == '__main__': + main() + +``` + +`tools/ibs_test/Makefile`: + +``` +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See tools/LICENSE for licensing details. + +THIS_TOOL_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +THIS_TOOL_NAME := ibs_test +TOOL_CFLAGS+=-I $(LIB_DIR) +TOOL_LDFLAGS+=-L $(LIB_DIR) -libs + +include $(THIS_TOOL_DIR)../common.mk + +``` + +`tools/ibs_test/ibs_test.c`: + +```c +/* + * Copyright (C) 2015-2017 Advanced Micro Devices, Inc. + * + * This is a simple application that uses libIBS to enable IBS and continually + * read IBS samples out of the driver until the user interrupts it. + * The IBS samples are not printed -- this application only tells you whether + * it is successfully pulling samples out of the driver. + * + * This file is distributed under the BSD license described in tools/LICENSE + */ + +#include +#include +#include +#include + +#include "ibs.h" + +// This program will attempt to read values out of the IBS driver to see that +// it is properly creating IBS samples and allowing users to read them. +// In the middle of the program, while trying to repeatedly read IBS samples +// from the driver, some useless work is performed in order to create +// pipeline operations for the IBS samples to describe. +int main(int argc, char *argv[]) +{ + + // By default, we will try to read 10 chunks of samples from the IBS driver + // However, a command-line argument allows you to pick the number of chunks + // to read, or (if you pass in a negative number) to read forever. + int num_rounds = 10; + if (argc > 2) + { + fprintf(stderr, "Too many arguments. Syntax: %s " + "[optional number of IBS sample reads to try]\n", argv[0]); + return -1; + } + else if (argc <= 1) + { + printf("No number of IBS read tries requested. Using default: %d\n", + num_rounds); + } + else + { + num_rounds = atoi(argv[1]); + if (num_rounds > 0) + printf("Attempting to get IBS samples %d times\n", num_rounds); + else + { + num_rounds = -1; + printf("Attempting to get IBS samples until this program is " + "killed.\n"); + } + } + + // Get the number of online CPUs and enable all of them in the IBS mask. + // The simplest way to do this is to allocate a char list with the maximum + // number of cores and set them all to one + int max_cores = get_nprocs_conf(); + char *core_map = calloc(max_cores, sizeof(char)); + //memset(core_map, 1, max_cores); + //^^^^^^^^^^^^ Add this back in if you remove the vvvvvv optional section + + // ============================ OPTIONAL =================================== + // The following section shows a method for getting the number of cores + // that are currently enabled. If you know you won't be hotplugging + // cores, feel free to skip doing this. + + // Run the following command to get a list of all the online processors: + //lscpu -p=cpu | grep -v \# + FILE *command_pipe; + char list_of_cpus[1024]; + memset(core_map, 0, max_cores); + command_pipe = popen("lscpu -p=cpu | grep -v \\#", "r"); + if (command_pipe == NULL) + { + fprintf(stderr, "Could not run lscpu in order to get online CPUs\n"); + exit(-1); + } + while (fgets(list_of_cpus, sizeof(list_of_cpus)-1, command_pipe) != NULL) + { + int core_num = atoi(list_of_cpus); + core_map[core_num] = 1; + } + if (pclose(command_pipe) != 0) + { + // lscpu failed. Let's try again the hard way. + printf("lscpu does not exist. Directly probing /proc/cpuinfo...\n"); + command_pipe = popen("cat /proc/cpuinfo | grep \"processor\" | awk '{print $3}'", "r"); + if (command_pipe == NULL) + { + fprintf(stderr, "Could not probe /proc/cpuinfo in order to get online CPUs\n"); + exit(-1); + } + while (fgets(list_of_cpus, sizeof(list_of_cpus)-1, command_pipe) != NULL) + { + int core_num = atoi(list_of_cpus); + core_map[core_num] = 1; + } + pclose(command_pipe); + } + // ============================ OPTIONAL =================================== + + + // The configuration list below shows some basic initialization parameters + // for the IBS system, as passed through libIBS. + // + // Once IBS is enabled, the IBS hardware will count every Nth instruction + // or op, as set by 16*IBS_MAX_CNT (with some low-order randomization). + // + // Whenever one of these samples is taken, it is saved into a kernel-level + // ring buffer that can be read out from the IBS driver device. + // + // It is possible to oll() on this device so that you wait for a certain + // number of IBS samples or a certain timeout before reading it, to prevent + // polling overheads from polluting the IBS trace and causing slowdowns. + // The IBS_POLL_TIMEOUT sets how long you should wait, while + // IBS_POLL_NUM_SAMPLES sets how many samples should be in the IBS buffer + // before a poll() call says the device is ready. + // Setting IBS_READ_ON_TIMEOUT will go ahead and read however many + // IBS samples are available after a timeout, even if it's less than + // IBS_POLL_NUM_SAMPLES. + // + // The IBS_CPU_LIST is an array that tells libIBS which CPUs to enable + // IBS on. + // + // Finally, IBS op and fetch sampling can be enabled/disabled + // separately using IBS_OP and IBS_FETCH. They are both enabled by default. + + ibs_option_list_t opts[] = + { + //{IBS_DEBUG, (ibs_val_t)1}, // Uncomment this to enable library debug + {IBS_POLL_TIMEOUT, (ibs_val_t)1}, // millisecond to wait for IBS samples + {IBS_POLL_NUM_SAMPLES, (ibs_val_t)2}, // Wait until this number of IBS + // samples exist before reading + {IBS_READ_ON_TIMEOUT, (ibs_val_t)1}, // If we have a timeout, try + // reading anything in the buffer + // even if we don't have + // IBS_POLL_NUM_SAMPLES in there. + {IBS_CPU_LIST, (ibs_val_t)core_map}, // Array of CPUs to use for IBS + {IBS_MAX_CNT, (ibs_val_t)1024}, // 16*{this val} is roughly how many + // instructions/ops between samples + {IBS_OP, (ibs_val_t)1}, // Enable IBS op sampling + {IBS_FETCH, (ibs_val_t)0}, // Disable IBS fetch sampling + }; + + + int status; + + // Start by initializing the IBS system using the options above. + status = ibs_initialize(opts, sizeof(opts)/sizeof(ibs_option_list_t), 0); + if (status < 0) { + fprintf(stderr, "Could not initialize IBS. %d\n", status); + return status; + } + + // Enable IBS on all of the CPUs that were initialized. + status = ibs_enable_all(); + if (status < 0) { + fprintf(stderr, "Could not enable IBS on all CPUs. %d\n", status); + return status; + } + + // Try to gather num_rounds chunks of IBS samples out of the driver. + // Do some work between each read so there are samples buffered up. + // (if num_rounds is < 0, do this forever. + for (int i = 0; i < num_rounds || num_rounds < 0; i++) + { + int rand_val = rand(); + /* Get some samples */ + ibs_sample_t samples[1024]; + ibs_sample_type_t types[1024]; + int num_samples; + + // Artificial work so that there are some op samples to read + for (int j = 0; j < 1000000; j++) + rand_val += (rand_val * rand_val); + + num_samples = ibs_sample(sizeof(samples)/sizeof(ibs_sample_t), + IBS_OP_SAMPLE | IBS_FETCH_SAMPLE, + (ibs_sample_t *)samples, (ibs_sample_type_t *)types); + + if (status < 0) { + fprintf(stderr, "Could not get an IBS sample! %d\n", status); + ibs_finalize(); + return status; + } + + printf("Got %d samples!\n", num_samples); + + // Do this useless printout so the compiler doesn't destroy are + // useless artificial work. + FILE *devnull = fopen("/dev/null", "w"); + fprintf(devnull, "Needless random value: %d\n", rand_val); + fclose(devnull); + } + + // Call IBS finalize after you're done to shut down cleanly. + ibs_finalize(); + + return 0; +} + +``` + +`tools/ibs_test/run_ibs_test.sh`: + +```sh +#!/bin/bash +# Copyright (c) 2015-2017 Advanced Micro Devices, Inc. All rights reserved. +# +# This file is made available under a 3-clause BSD license. +# See tools/LICENSE for licensing details. +BASE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +if [ ! -f ${BASE_DIR}/ibs_test ]; then + echo -e "${BASE_DIR}/ibs_test does not exist. Exiting." + exit -1 +fi + +if ldd ${BASE_DIR}/ibs_test | grep -q "libibs.so => not found"; then + echo -e "libibs.so is not in the LD_LIBRARY_PATH. Trying to add it.." + if [ ! -f ${BASE_DIR}/../../lib/libibs.so ]; then + echo -e "${BASE_DIR}/../../lib/libibs.so does not exist. Trying to build it.." + pushd ${BASE_DIR}/../../lib/ + make + if [ $? -ne 0 ]; then + echo -e "Failed to build libibs.so. Exiting." + exit -1 + fi + popd + fi + export LD_LIBRARY_PATH=${BASE_DIR}/../../lib/:$LD_LIBRARY_PATH +fi + +if [ ! -f ${BASE_DIR}/../../lib/libibs.so ]; then + echo -e "Cannot find ${BASE_DIR}/../../lib/libibs.so. Exiting." + exit -1 +else + ${BASE_DIR}/ibs_test +fi + +``` \ No newline at end of file