@@ -157,29 +157,48 @@ static int _replacement_getcpu(void* arg1, void* arg2, void* arg3) {
157157 return (int )syscall (SYS_getcpu , arg1 , arg2 , arg3 );
158158}
159159
160- static void _inject_trampoline (struct ParsedElf * parsedElf , const char * vdsoFnName ,
161- void * replacementFn ) {
162- const Elf64_Sym * symbol = _findSymbol (parsedElf , vdsoFnName );
163- if (symbol == NULL ) {
164- warning ("Couldn't find symbol '%s' to override" , vdsoFnName );
165- return ;
160+ // Inject a trampoline that uses a relative jump. Only needs 5 bytes, but requires
161+ // that the offset fits in an i32.
162+ //
163+ // Returns injected trampoline size on success, or 0 if the trampoline couldn't be injected.
164+ static size_t _inject_trampoline_relative (void * start , size_t symbolSize , void * replacementFn ) {
165+ intptr_t jmp_offset = (intptr_t )replacementFn - ((intptr_t )start + 5 );
166+
167+ if (jmp_offset > INT32_MAX || jmp_offset < INT32_MIN ) {
168+ trace ("Offset from %p to %p doesn't fit in i32" , start , replacementFn );
169+ return 0 ;
170+ }
171+
172+ const size_t trampolineSize = 5 ;
173+ if (symbolSize < trampolineSize ) {
174+ trace ("Can't inject %zd byte trampoline into %zd byte symbol" , trampolineSize , symbolSize );
175+ return 0 ;
166176 }
167177
168- // Manually calculated trampoline size. Later validated in assertion.
178+ uint8_t * current = start ;
179+ // opcode for jmp
180+ * (current ++ ) = 0xe9 ;
181+ // jmp offset
182+ * (int32_t * )current = (int32_t )jmp_offset ;
183+ current += sizeof (int32_t );
184+
185+ size_t actualTrampolineSize = (size_t )current - (size_t )start ;
186+ assert (actualTrampolineSize == trampolineSize );
187+ return actualTrampolineSize ;
188+ }
189+
190+ // Inject a trampoline using an absolute jump. Less efficient at runtime and
191+ // needs 13 bytes, but can jump to any target address.
192+ //
193+ // Returns injected trampoline size on success, or 0 if the trampoline couldn't be injected.
194+ static size_t _inject_trampoline_absolute (void * start , size_t symbolSize , void * replacementFn ) {
169195 const size_t trampolineSize = 13 ;
170196
171- if (symbol -> st_size < trampolineSize ) {
172- // We *could* just log a warning here and return without injecting the
173- // trampoline. Shim-side warnings are currently a bit hidden, though.
174- // Better to err on the size of not accidentally proceeding with a
175- // potentially invalid simulation.
176- //
177- // TODO: Revisit when https://github.com/shadow/shadow/issues/2023 is fixed.
178- panic ("Can't inject %zd byte trampoline into %zd byte symbol '%s'" , trampolineSize ,
179- symbol -> st_size , vdsoFnName );
197+ if (symbolSize < trampolineSize ) {
198+ trace ("Can't inject %zd byte trampoline into %zd byte symbol" , trampolineSize , symbolSize );
199+ return 0 ;
180200 }
181201
182- uint8_t * start = (void * )parsedElf -> hdr + symbol -> st_value ;
183202 uint8_t * current = start ;
184203
185204 // movabs $...,%r10
@@ -193,11 +212,43 @@ static void _inject_trampoline(struct ParsedElf* parsedElf, const char* vdsoFnNa
193212 * (current ++ ) = 0xff ;
194213 * (current ++ ) = 0xe2 ;
195214
196- // Validate trampolineSize.
197- const size_t actualTrampolineSize = (size_t )current - (size_t )start ;
198- // In debug builds require equality.
215+ size_t actualTrampolineSize = (size_t )current - (size_t )start ;
199216 assert (actualTrampolineSize == trampolineSize );
200- // Otherwise just ensure we didn't actually clobber another symbol.
217+ return actualTrampolineSize ;
218+ }
219+
220+ static void _inject_trampoline (struct ParsedElf * parsedElf , const char * vdsoFnName ,
221+ void * replacementFn ) {
222+ const Elf64_Sym * symbol = _findSymbol (parsedElf , vdsoFnName );
223+ if (symbol == NULL ) {
224+ // This could happen e.g. if vdso is disabled at the system level.
225+ warning ("Couldn't find symbol '%s' to override" , vdsoFnName );
226+ return ;
227+ }
228+
229+ uint8_t * start = (void * )parsedElf -> hdr + symbol -> st_value ;
230+
231+ size_t actualTrampolineSize =
232+ _inject_trampoline_relative (start , symbol -> st_size , replacementFn );
233+ if (actualTrampolineSize == 0 ) {
234+ actualTrampolineSize = _inject_trampoline_absolute (start , symbol -> st_size , replacementFn );
235+ }
236+ // TODO: Some other trampoline strategies if neither of the above work:
237+ //
238+ // * In case the replacementFn is more than a 32-bit offset away, create a secondary
239+ // trampoline that *is* < 32-bit-offset away, e.g. using `mmap` with a supplied hint.
240+ //
241+ // * Inject `ud2; ret`, or even just `ud2`. `ud2` is only 2 bytes and raises
242+ // SIGILL; we could get control in the SIGILL signal handler and figure out
243+ // which patched function we're trying to execute by inspecting the
244+ // instruction pointer in the siginfo_t.
245+
246+ if (actualTrampolineSize == 0 ) {
247+ // TODO: Make make this a warning or error when shim-side logs are more visible.
248+ panic ("Couldn't patch symbol '%s'" , vdsoFnName );
249+ }
250+
251+ // Validate that we didn't actually clobber another symbol.
201252 if (symbol -> st_size < actualTrampolineSize ) {
202253 panic ("Accidentally wrote %zd byte trampoline into %zd byte symbol %s" ,
203254 actualTrampolineSize , symbol -> st_size , vdsoFnName );
0 commit comments