1
0

vprof_timings.txt 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // vprof_timings.txt — Source Scramble memory patches that make Source Engine
  2. // VProf captures record real per-frame timing data.
  3. //
  4. // Target: Team Fortress 2 build 11068238 (Linux x86 and x86_64),
  5. // addons/sourcemod/extensions/sourcescramble.ext.
  6. //
  7. // Background
  8. // ----------
  9. // The VProf recorder writes the *current*-frame counters:
  10. // CVProfRecorder::Record_WriteTimings_R() reads pIn->m_nCurFrameCalls
  11. // and pIn->m_CurFrameTime
  12. // but engine/vprof_engine.cpp calls VProfRecord_Snapshot() after
  13. // CVProfile::MarkFrame() has already rotated those counters into the
  14. // *previous*-frame fields (m_nPrevFrameCalls / m_PrevFrameTime) and cleared
  15. // the current ones. The result is that every .vprof capture contains a
  16. // correct call tree with all-zero times and call counts.
  17. //
  18. // CVProfExport::CalculateBudgetGroupTimes_Recursive() additionally calls
  19. // CVProfNode::ClearPrevTime() before the recorder runs, which would wipe the
  20. // previous-frame data even if the recorder read it.
  21. //
  22. // This patch set:
  23. // 1. redirects the three current-frame field reads in
  24. // Record_WriteTimings_R() to the matching previous-frame fields, and
  25. // 2. forces VProfRecord_IsPlayingBack() to return true so that
  26. // CalculateBudgetGroupTimes_Recursive() skips ClearPrevTime().
  27. //
  28. // The recorder still runs after the export, but the previous-frame data now
  29. // survives until it does. The recorded frame carries the tick of the frame
  30. // it was snapshotted in (a harmless one-frame label offset; the timing values
  31. // themselves are correct).
  32. //
  33. // The signatures are resolved from the unstripped engine binary's ELF
  34. // .symtab via SourceMod's `@symbol` support (TF2 reports hidden symbols, so
  35. // SourceMod uses MemoryUtils::ResolveSymbol rather than dlsym). This keeps
  36. // them independent of ASLR / load-base relocation. The byte offsets are
  37. // relative to the function entry and are build-specific; regenerate them
  38. // whenever the engine is updated.
  39. "Games"
  40. {
  41. "tf"
  42. {
  43. "Signatures"
  44. {
  45. // CVProfRecorder::Record_WriteTimings_R(CVProfNode const*)
  46. "CVProfRecorder::Record_WriteTimings_R()"
  47. {
  48. "library" "engine"
  49. "linux" "@_ZN14CVProfRecorder21Record_WriteTimings_REPK10CVProfNode"
  50. "linux64" "@_ZN14CVProfRecorder21Record_WriteTimings_REPK10CVProfNode"
  51. }
  52. // bool VProfRecord_IsPlayingBack()
  53. "VProfRecord_IsPlayingBack()"
  54. {
  55. "library" "engine"
  56. "linux" "@_Z25VProfRecord_IsPlayingBackv"
  57. "linux64" "@_Z25VProfRecord_IsPlayingBackv"
  58. }
  59. }
  60. "MemPatches"
  61. {
  62. // pIn->m_nCurFrameCalls (0x38) -> pIn->m_nPrevFrameCalls (0x44).
  63. // 32-bit: mov eax,[esi+0x38] at +0x1B, displacement at +0x1D.
  64. // 64-bit: mov eax,[rsi+0x4C] at +0x1C, displacement at +0x1E.
  65. "CVProfRecorder::Record_WriteTimings_R()::UsePrevCalls"
  66. {
  67. "signature" "CVProfRecorder::Record_WriteTimings_R()"
  68. "linux"
  69. {
  70. "offset" "1Dh"
  71. "verify" "\x38"
  72. "patch" "\x44"
  73. }
  74. "linux64"
  75. {
  76. "offset" "1Eh"
  77. "verify" "\x4C"
  78. "patch" "\x58"
  79. }
  80. }
  81. // pIn->m_CurFrameTime -> pIn->m_PrevFrameTime.
  82. //
  83. // 32-bit CCycleCount is an unaligned int64 and the compiler reads
  84. // its high dword for the 64x32 multiplication:
  85. // imul ecx,[esi+0x40],1000000 (high, 0x40 -> 0x4C, +0x53)
  86. // mul [esi+0x3C] (low, 0x3C -> 0x48, +0x5F)
  87. // 64-bit is a single 64-bit load:
  88. // imul rax,[rbx+0x50],1000000 (0x50 -> 0x60, +0x66)
  89. "CVProfRecorder::Record_WriteTimings_R()::UsePrevTimeHigh"
  90. {
  91. "signature" "CVProfRecorder::Record_WriteTimings_R()"
  92. "linux"
  93. {
  94. "offset" "53h"
  95. "verify" "\x40"
  96. "patch" "\x4C"
  97. }
  98. "linux64"
  99. {
  100. "offset" "66h"
  101. "verify" "\x50"
  102. "patch" "\x60"
  103. }
  104. }
  105. // 32-bit only: low dword of m_CurFrameTime.
  106. "CVProfRecorder::Record_WriteTimings_R()::UsePrevTimeLow"
  107. {
  108. "signature" "CVProfRecorder::Record_WriteTimings_R()"
  109. "linux"
  110. {
  111. "offset" "5Fh"
  112. "verify" "\x3C"
  113. "patch" "\x48"
  114. }
  115. }
  116. // Force VProfRecord_IsPlayingBack() to return true so the export
  117. // path does not clear the previous-frame data before the recorder
  118. // reads it:
  119. // cmp dword [...], 2 ; sete al ; ret
  120. // becomes
  121. // cmp dword [...], 2 ; mov al,1 ; nop ; ret
  122. "VProfRecord_IsPlayingBack()::SkipClearPrev"
  123. {
  124. "signature" "VProfRecord_IsPlayingBack()"
  125. "linux"
  126. {
  127. "offset" "7h"
  128. "verify" "\x0F\x94\xC0"
  129. "patch" "\xB0\x01\x90"
  130. }
  131. "linux64"
  132. {
  133. "offset" "7h"
  134. "verify" "\x0F\x94\xC0"
  135. "patch" "\xB0\x01\x90"
  136. }
  137. }
  138. }
  139. }
  140. }