| OLD | NEW |
| 1 /* Copyright (c) 2007, Google Inc. | 1 /* Copyright (c) 2007, Google Inc. |
| 2 * All rights reserved. | 2 * All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| (...skipping 11 matching lines...) Expand all Loading... |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 * | 29 * |
| 30 * --- | 30 * --- |
| 31 * Author: Joi Sigurdsson | 31 * Author: Joi Sigurdsson |
| 32 * Author: Scott Francis | |
| 33 * | 32 * |
| 34 * Implementation of PreamblePatcher | 33 * Implementation of PreamblePatcher |
| 35 */ | 34 */ |
| 36 | 35 |
| 37 #include "preamble_patcher.h" | 36 #include "preamble_patcher.h" |
| 38 | 37 |
| 39 #include "mini_disassembler.h" | 38 #include "mini_disassembler.h" |
| 40 | 39 |
| 41 // Definitions of assembly statements we need | 40 // Definitions of assembly statements we need |
| 42 #define ASM_JMP32REL 0xE9 | 41 #define ASM_JMP32REL 0xE9 |
| 43 #define ASM_INT3 0xCC | 42 #define ASM_INT3 0xCC |
| 44 #define ASM_NOP 0x90 | |
| 45 // X64 opcodes | |
| 46 #define ASM_MOVRAX_IMM 0xB8 | |
| 47 #define ASM_REXW 0x48 | |
| 48 #define ASM_JMP 0xFF | |
| 49 #define ASM_JMP_RAX 0xE0 | |
| 50 #define ASM_PUSH 0x68 | |
| 51 #define ASM_RET 0xC3 | |
| 52 | 43 |
| 53 namespace sidestep { | 44 namespace sidestep { |
| 54 | 45 |
| 55 SideStepError PreamblePatcher::RawPatchWithStub( | 46 SideStepError PreamblePatcher::RawPatchWithStub( |
| 56 void* target_function, | 47 void* target_function, |
| 57 void* replacement_function, | 48 void *replacement_function, |
| 58 unsigned char* preamble_stub, | 49 unsigned char* preamble_stub, |
| 59 unsigned long stub_size, | 50 unsigned long stub_size, |
| 60 unsigned long* bytes_needed) { | 51 unsigned long* bytes_needed) { |
| 61 if ((NULL == target_function) || | 52 if ((NULL == target_function) || |
| 62 (NULL == replacement_function) || | 53 (NULL == replacement_function) || |
| 63 (NULL == preamble_stub)) { | 54 (NULL == preamble_stub)) { |
| 64 SIDESTEP_ASSERT(false && | 55 SIDESTEP_ASSERT(false && |
| 65 "Invalid parameters - either pTargetFunction or " | 56 "Invalid parameters - either pTargetFunction or " |
| 66 "pReplacementFunction or pPreambleStub were NULL."); | 57 "pReplacementFunction or pPreambleStub were NULL."); |
| 67 return SIDESTEP_INVALID_PARAMETER; | 58 return SIDESTEP_INVALID_PARAMETER; |
| 68 } | 59 } |
| 69 | 60 |
| 70 // TODO(V7:joi) Siggi and I just had a discussion and decided that both | 61 // TODO(V7:joi) Siggi and I just had a discussion and decided that both |
| 71 // patching and unpatching are actually unsafe. We also discussed a | 62 // patching and unpatching are actually unsafe. We also discussed a |
| 72 // method of making it safe, which is to freeze all other threads in the | 63 // method of making it safe, which is to freeze all other threads in the |
| 73 // process, check their thread context to see if their eip is currently | 64 // process, check their thread context to see if their eip is currently |
| 74 // inside the block of instructions we need to copy to the stub, and if so | 65 // inside the block of instructions we need to copy to the stub, and if so |
| 75 // wait a bit and try again, then unfreeze all threads once we've patched. | 66 // wait a bit and try again, then unfreeze all threads once we've patched. |
| 76 // Not implementing this for now since we're only using SideStep for unit | 67 // Not implementing this for now since we're only using SideStep for unit |
| 77 // testing, but if we ever use it for production code this is what we | 68 // testing, but if we ever use it for production code this is what we |
| 78 // should do. | 69 // should do. |
| 79 // | 70 // |
| 80 // NOTE: Stoyan suggests we can write 8 or even 10 bytes atomically using | 71 // NOTE: Stoyan suggests we can write 8 or even 10 bytes atomically using |
| 81 // FPU instructions, and on newer processors we could use cmpxchg8b or | 72 // FPU instructions, and on newer processors we could use cmpxchg8b or |
| 82 // cmpxchg16b. So it might be possible to do the patching/unpatching | 73 // cmpxchg16b. So it might be possible to do the patching/unpatching |
| 83 // atomically and avoid having to freeze other threads. Note though, that | 74 // atomically and avoid having to freeze other threads. Note though, that |
| 84 // doing it atomically does not help if one of the other threads happens | 75 // doing it atomically does not help if one of the other threads happens |
| 85 // to have its eip in the middle of the bytes you change while you change | 76 // to have its eip in the middle of the bytes you change while you change |
| 86 // them. | 77 // them. |
| 87 unsigned char* target = reinterpret_cast<unsigned char*>(target_function); | |
| 88 unsigned int required_trampoline_bytes = 0; | |
| 89 const unsigned int kRequiredStubJumpBytes = 5; | |
| 90 const unsigned int kRequiredTargetPatchBytes = 5; | |
| 91 | 78 |
| 92 // Initialize the stub with INT3's just in case. | 79 // First, deal with a special case that we see with functions that |
| 93 if (stub_size) { | 80 // point into an IAT table (including functions linked statically |
| 94 memset(preamble_stub, 0xcc, stub_size); | 81 // into the application): these function already starts with |
| 82 // ASM_JMP32REL. For instance, malloc() might be implemented as a |
| 83 // JMP to __malloc(). In that case, we replace the destination of |
| 84 // the JMP (__malloc), rather than the JMP itself (malloc). This |
| 85 // way we get the correct behavior no matter how malloc gets called. |
| 86 void *new_target = ResolveTarget(target_function); |
| 87 if (new_target != target_function) { // we're in the IAT case |
| 88 // I'd like to just say "target = new_target", but I can't, |
| 89 // because the new target will need to have its protections set. |
| 90 return RawPatchWithStubAndProtections(new_target, replacement_function, |
| 91 preamble_stub, stub_size, |
| 92 bytes_needed); |
| 95 } | 93 } |
| 96 if (kIs64BitBinary) { | 94 unsigned char* target = reinterpret_cast<unsigned char*>(new_target); |
| 97 // In 64-bit mode JMP instructions are always relative to RIP. If the | |
| 98 // replacement - target offset is > 2GB, we can't JMP to the replacement | |
| 99 // function. In this case, we're going to use a trampoline - that is, | |
| 100 // we're going to do a relative jump to a small chunk of code in the stub | |
| 101 // that will then do the absolute jump to the replacement function. By | |
| 102 // doing this, we only need to patch 5 bytes in the target function, as | |
| 103 // opposed to patching 12 bytes if we were to do an absolute jump. | |
| 104 // | |
| 105 // Note that the first byte of the trampoline is a NOP instruction. This | |
| 106 // is used as a trampoline signature that will be detected when unpatching | |
| 107 // the function. | |
| 108 // | |
| 109 // jmp <trampoline> | |
| 110 // | |
| 111 // trampoline: | |
| 112 // nop | |
| 113 // mov rax, <replacement_function> | |
| 114 // jmp rax | |
| 115 // | |
| 116 __int64 replacement_target_offset = reinterpret_cast<__int64>( | |
| 117 replacement_function) - reinterpret_cast<__int64>(target) - 5; | |
| 118 if (replacement_target_offset > INT_MAX | |
| 119 || replacement_target_offset < INT_MIN) { | |
| 120 // The stub needs to be within 2GB of the target for the trampoline to | |
| 121 // work! | |
| 122 __int64 trampoline_offset = reinterpret_cast<__int64>(preamble_stub) | |
| 123 - reinterpret_cast<__int64>(target) - 5; | |
| 124 if (trampoline_offset > INT_MAX || trampoline_offset < INT_MIN) { | |
| 125 // We're screwed. | |
| 126 SIDESTEP_ASSERT(false | |
| 127 && "Preamble stub is too far from target to patch."); | |
| 128 return SIDESTEP_UNEXPECTED; | |
| 129 } | |
| 130 required_trampoline_bytes = 13; | |
| 131 } | |
| 132 } | |
| 133 | 95 |
| 134 // Let's disassemble the preamble of the target function to see if we can | 96 // Let's disassemble the preamble of the target function to see if we can |
| 135 // patch, and to see how much of the preamble we need to take. We need 5 | 97 // patch, and to see how much of the preamble we need to take. We need 5 |
| 136 // bytes for our jmp instruction, so let's find the minimum number of | 98 // bytes for our jmp instruction, so let's find the minimum number of |
| 137 // instructions to get 5 bytes. | 99 // instructions to get 5 bytes. |
| 138 MiniDisassembler disassembler; | 100 MiniDisassembler disassembler; |
| 139 unsigned int preamble_bytes = 0; | 101 unsigned int preamble_bytes = 0; |
| 140 unsigned int stub_bytes = 0; | 102 while (preamble_bytes < 5) { |
| 141 while (preamble_bytes < kRequiredTargetPatchBytes) { | |
| 142 unsigned int cur_bytes = 0; | |
| 143 InstructionType instruction_type = | 103 InstructionType instruction_type = |
| 144 disassembler.Disassemble(target + preamble_bytes, cur_bytes); | 104 disassembler.Disassemble(target + preamble_bytes, preamble_bytes); |
| 145 if (IT_JUMP == instruction_type) { | 105 if (IT_JUMP == instruction_type) { |
| 146 unsigned int jump_bytes = 0; | 106 SIDESTEP_ASSERT(false && |
| 147 SideStepError jump_ret = SIDESTEP_JUMP_INSTRUCTION; | 107 "Unable to patch because there is a jump instruction " |
| 148 if (IsShortConditionalJump(target + preamble_bytes, cur_bytes)) { | 108 "in the first 5 bytes."); |
| 149 jump_ret = PatchShortConditionalJump(target + preamble_bytes, cur_bytes, | 109 return SIDESTEP_JUMP_INSTRUCTION; |
| 150 preamble_stub + stub_bytes, | |
| 151 &jump_bytes, | |
| 152 stub_size - stub_bytes); | |
| 153 } else if (IsNearConditionalJump(target + preamble_bytes, cur_bytes) || | |
| 154 IsNearRelativeJump(target + preamble_bytes, cur_bytes) || | |
| 155 IsNearAbsoluteCall(target + preamble_bytes, cur_bytes) || | |
| 156 IsNearRelativeCall(target + preamble_bytes, cur_bytes)) { | |
| 157 jump_ret = PatchNearJumpOrCall(target + preamble_bytes, cur_bytes, | |
| 158 preamble_stub + stub_bytes, &jump_bytes, | |
| 159 stub_size - stub_bytes); | |
| 160 } | |
| 161 if (jump_ret != SIDESTEP_SUCCESS) { | |
| 162 SIDESTEP_ASSERT(false && | |
| 163 "Unable to patch because there is an unhandled branch " | |
| 164 "instruction in the initial preamble bytes."); | |
| 165 return SIDESTEP_JUMP_INSTRUCTION; | |
| 166 } | |
| 167 stub_bytes += jump_bytes; | |
| 168 } else if (IT_RETURN == instruction_type) { | 110 } else if (IT_RETURN == instruction_type) { |
| 169 SIDESTEP_ASSERT(false && | 111 SIDESTEP_ASSERT(false && |
| 170 "Unable to patch because function is too short"); | 112 "Unable to patch because function is too short"); |
| 171 return SIDESTEP_FUNCTION_TOO_SMALL; | 113 return SIDESTEP_FUNCTION_TOO_SMALL; |
| 172 } else if (IT_GENERIC == instruction_type) { | 114 } else if (IT_GENERIC != instruction_type) { |
| 173 if (IsMovWithDisplacement(target + preamble_bytes, cur_bytes)) { | |
| 174 unsigned int mov_bytes = 0; | |
| 175 if (PatchMovWithDisplacement(target + preamble_bytes, cur_bytes, | |
| 176 preamble_stub + stub_bytes, &mov_bytes, | |
| 177 stub_size - stub_bytes) | |
| 178 != SIDESTEP_SUCCESS) { | |
| 179 return SIDESTEP_UNSUPPORTED_INSTRUCTION; | |
| 180 } | |
| 181 stub_bytes += mov_bytes; | |
| 182 } else { | |
| 183 memcpy(reinterpret_cast<void*>(preamble_stub + stub_bytes), | |
| 184 reinterpret_cast<void*>(target + preamble_bytes), cur_bytes); | |
| 185 stub_bytes += cur_bytes; | |
| 186 } | |
| 187 } else { | |
| 188 SIDESTEP_ASSERT(false && | 115 SIDESTEP_ASSERT(false && |
| 189 "Disassembler encountered unsupported instruction " | 116 "Disassembler encountered unsupported instruction " |
| 190 "(either unused or unknown"); | 117 "(either unused or unknown"); |
| 191 return SIDESTEP_UNSUPPORTED_INSTRUCTION; | 118 return SIDESTEP_UNSUPPORTED_INSTRUCTION; |
| 192 } | 119 } |
| 193 preamble_bytes += cur_bytes; | |
| 194 } | 120 } |
| 195 | 121 |
| 196 if (NULL != bytes_needed) | 122 if (NULL != bytes_needed) |
| 197 *bytes_needed = stub_bytes + kRequiredStubJumpBytes | 123 *bytes_needed = preamble_bytes + 5; |
| 198 + required_trampoline_bytes; | |
| 199 | 124 |
| 200 // Inv: cbPreamble is the number of bytes (at least 5) that we need to take | 125 // Inv: cbPreamble is the number of bytes (at least 5) that we need to take |
| 201 // from the preamble to have whole instructions that are 5 bytes or more | 126 // from the preamble to have whole instructions that are 5 bytes or more |
| 202 // in size total. The size of the stub required is cbPreamble + | 127 // in size total. The size of the stub required is cbPreamble + size of |
| 203 // kRequiredStubJumpBytes (5) + required_trampoline_bytes (0 or 13) | 128 // jmp (5) |
| 204 if (stub_bytes + kRequiredStubJumpBytes + required_trampoline_bytes | 129 if (preamble_bytes + 5 > stub_size) { |
| 205 > stub_size) { | |
| 206 SIDESTEP_ASSERT(false); | 130 SIDESTEP_ASSERT(false); |
| 207 return SIDESTEP_INSUFFICIENT_BUFFER; | 131 return SIDESTEP_INSUFFICIENT_BUFFER; |
| 208 } | 132 } |
| 209 | 133 |
| 134 // First, copy the preamble that we will overwrite. |
| 135 memcpy(reinterpret_cast<void*>(preamble_stub), |
| 136 reinterpret_cast<void*>(target), preamble_bytes); |
| 137 |
| 210 // Now, make a jmp instruction to the rest of the target function (minus the | 138 // Now, make a jmp instruction to the rest of the target function (minus the |
| 211 // preamble bytes we moved into the stub) and copy it into our preamble-stub. | 139 // preamble bytes we moved into the stub) and copy it into our preamble-stub. |
| 212 // find address to jump to, relative to next address after jmp instruction | 140 // find address to jump to, relative to next address after jmp instruction |
| 213 #ifdef _MSC_VER | 141 #ifdef _MSC_VER |
| 214 #pragma warning(push) | 142 #pragma warning(push) |
| 215 #pragma warning(disable:4244) | 143 #pragma warning(disable:4244) |
| 216 #endif | 144 #endif |
| 217 int relative_offset_to_target_rest | 145 int relative_offset_to_target_rest |
| 218 = ((reinterpret_cast<unsigned char*>(target) + preamble_bytes) - | 146 = ((reinterpret_cast<unsigned char*>(target) + preamble_bytes) - |
| 219 (preamble_stub + stub_bytes + kRequiredStubJumpBytes)); | 147 (preamble_stub + preamble_bytes + 5)); |
| 220 #ifdef _MSC_VER | 148 #ifdef _MSC_VER |
| 221 #pragma warning(pop) | 149 #pragma warning(pop) |
| 222 #endif | 150 #endif |
| 223 // jmp (Jump near, relative, displacement relative to next instruction) | 151 // jmp (Jump near, relative, displacement relative to next instruction) |
| 224 preamble_stub[stub_bytes] = ASM_JMP32REL; | 152 preamble_stub[preamble_bytes] = ASM_JMP32REL; |
| 225 // copy the address | 153 // copy the address |
| 226 memcpy(reinterpret_cast<void*>(preamble_stub + stub_bytes + 1), | 154 memcpy(reinterpret_cast<void*>(preamble_stub + preamble_bytes + 1), |
| 227 reinterpret_cast<void*>(&relative_offset_to_target_rest), 4); | 155 reinterpret_cast<void*>(&relative_offset_to_target_rest), 4); |
| 228 | 156 |
| 229 if (kIs64BitBinary && required_trampoline_bytes != 0) { | |
| 230 // Construct the trampoline | |
| 231 unsigned int trampoline_pos = stub_bytes + kRequiredStubJumpBytes; | |
| 232 preamble_stub[trampoline_pos] = ASM_NOP; | |
| 233 preamble_stub[trampoline_pos + 1] = ASM_REXW; | |
| 234 preamble_stub[trampoline_pos + 2] = ASM_MOVRAX_IMM; | |
| 235 memcpy(reinterpret_cast<void*>(preamble_stub + trampoline_pos + 3), | |
| 236 reinterpret_cast<void*>(&replacement_function), | |
| 237 sizeof(void *)); | |
| 238 preamble_stub[trampoline_pos + 11] = ASM_JMP; | |
| 239 preamble_stub[trampoline_pos + 12] = ASM_JMP_RAX; | |
| 240 | |
| 241 // Now update replacement_function to point to the trampoline | |
| 242 replacement_function = preamble_stub + trampoline_pos; | |
| 243 } | |
| 244 | |
| 245 // Inv: preamble_stub points to assembly code that will execute the | 157 // Inv: preamble_stub points to assembly code that will execute the |
| 246 // original function by first executing the first cbPreamble bytes of the | 158 // original function by first executing the first cbPreamble bytes of the |
| 247 // preamble, then jumping to the rest of the function. | 159 // preamble, then jumping to the rest of the function. |
| 248 | 160 |
| 249 // Overwrite the first 5 bytes of the target function with a jump to our | 161 // Overwrite the first 5 bytes of the target function with a jump to our |
| 250 // replacement function. | 162 // replacement function. |
| 251 // (Jump near, relative, displacement relative to next instruction) | 163 // (Jump near, relative, displacement relative to next instruction) |
| 252 target[0] = ASM_JMP32REL; | 164 target[0] = ASM_JMP32REL; |
| 253 | 165 |
| 254 // Find offset from instruction after jmp, to the replacement function. | 166 // Find offset from instruction after jmp, to the replacement function. |
| 255 #ifdef _MSC_VER | 167 #ifdef _MSC_VER |
| 256 #pragma warning(push) | 168 #pragma warning(push) |
| 257 #pragma warning(disable:4244) | 169 #pragma warning(disable:4244) |
| 258 #endif | 170 #endif |
| 259 int offset_to_replacement_function = | 171 int offset_to_replacement_function = |
| 260 reinterpret_cast<unsigned char*>(replacement_function) - | 172 reinterpret_cast<unsigned char*>(replacement_function) - |
| 261 reinterpret_cast<unsigned char*>(target) - 5; | 173 reinterpret_cast<unsigned char*>(target) - 5; |
| 262 #ifdef _MSC_VER | 174 #ifdef _MSC_VER |
| 263 #pragma warning(pop) | 175 #pragma warning(pop) |
| 264 #endif | 176 #endif |
| 265 // complete the jmp instruction | 177 // complete the jmp instruction |
| 266 memcpy(reinterpret_cast<void*>(target + 1), | 178 memcpy(reinterpret_cast<void*>(target + 1), |
| 267 reinterpret_cast<void*>(&offset_to_replacement_function), 4); | 179 reinterpret_cast<void*>(&offset_to_replacement_function), 4); |
| 268 | |
| 269 // Set any remaining bytes that were moved to the preamble-stub to INT3 so | 180 // Set any remaining bytes that were moved to the preamble-stub to INT3 so |
| 270 // as not to cause confusion (otherwise you might see some strange | 181 // as not to cause confusion (otherwise you might see some strange |
| 271 // instructions if you look at the disassembly, or even invalid | 182 // instructions if you look at the disassembly, or even invalid |
| 272 // instructions). Also, by doing this, we will break into the debugger if | 183 // instructions). Also, by doing this, we will break into the debugger if |
| 273 // some code calls into this portion of the code. If this happens, it | 184 // some code calls into this portion of the code. If this happens, it |
| 274 // means that this function cannot be patched using this patcher without | 185 // means that this function cannot be patched using this patcher without |
| 275 // further thought. | 186 // further thought. |
| 276 if (preamble_bytes > kRequiredTargetPatchBytes) { | 187 if (preamble_bytes > 5) { |
| 277 memset(reinterpret_cast<void*>(target + kRequiredTargetPatchBytes), | 188 memset(reinterpret_cast<void*>(target + 5), ASM_INT3, preamble_bytes - 5); |
| 278 ASM_INT3, preamble_bytes - kRequiredTargetPatchBytes); | |
| 279 } | 189 } |
| 280 | 190 |
| 281 // Inv: The memory pointed to by target_function now points to a relative | 191 // Inv: The memory pointed to by target_function now points to a relative |
| 282 // jump instruction that jumps over to the preamble_stub. The preamble | 192 // jump instruction that jumps over to the preamble_stub. The preamble |
| 283 // stub contains the first stub_size bytes of the original target | 193 // stub contains the first stub_size bytes of the original target |
| 284 // function's preamble code, followed by a relative jump back to the next | 194 // function's preamble code, followed by a relative jump back to the next |
| 285 // instruction after the first cbPreamble bytes. | 195 // instruction after the first cbPreamble bytes. |
| 286 // | 196 |
| 287 // In 64-bit mode the memory pointed to by target_function *may* point to a | |
| 288 // relative jump instruction that jumps to a trampoline which will then | |
| 289 // perform an absolute jump to the replacement function. The preamble stub | |
| 290 // still contains the original target function's preamble code, followed by a | |
| 291 // jump back to the instructions after the first preamble bytes. | |
| 292 // | |
| 293 return SIDESTEP_SUCCESS; | 197 return SIDESTEP_SUCCESS; |
| 294 } | 198 } |
| 295 | 199 |
| 296 }; // namespace sidestep | 200 }; // namespace sidestep |
| OLD | NEW |