breakpoints.c revision ce377d567ccc0b14693619b69ebe0ac6deb0ba90
1d44c6b8b090b8b7aa9d971d9e0bfd848732a3071Juan Cespedes#if HAVE_CONFIG_H 2d44c6b8b090b8b7aa9d971d9e0bfd848732a3071Juan Cespedes#include "config.h" 3d44c6b8b090b8b7aa9d971d9e0bfd848732a3071Juan Cespedes#endif 4d44c6b8b090b8b7aa9d971d9e0bfd848732a3071Juan Cespedes 55b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes#include <stdlib.h> 67186e2af704f4458e6383e8a92482594db29b597Juan Cespedes#include <string.h> 75b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes#include <assert.h> 85b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 9f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes#ifdef __powerpc__ 10f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes#include <sys/ptrace.h> 11f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes#endif 12f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes 13cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes#include "ltrace.h" 14cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes#include "options.h" 15cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes#include "debug.h" 16cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes#include "dict.h" 177186e2af704f4458e6383e8a92482594db29b597Juan Cespedes#include "elf.h" 185b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 195b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes/*****************************************************************************/ 205b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 21f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesstruct breakpoint * 22f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesaddress2bpstruct(struct process *proc, void *addr) { 23cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes return dict_find_entry(proc->breakpoints, addr); 245b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes} 255b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 269a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienandvoid 272d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienandinsert_breakpoint(struct process *proc, void *addr, 28f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedes struct library_symbol *libsym) { 292d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand struct breakpoint *sbp; 30b3f8fef12fccb0914b7b28725f42192c279d31c9Petr Machata debug(1, "symbol=%s, addr=%p", libsym?libsym->name:"(nil)", addr); 315b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 322d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand if (!addr) 332d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand return; 349a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand 352d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand if (libsym) 369a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand libsym->needs_init = 0; 379a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand 38cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes sbp = dict_find_entry(proc->breakpoints, addr); 39cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes if (!sbp) { 409a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand sbp = calloc(1, sizeof(struct breakpoint)); 41cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes if (!sbp) { 422d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand return; /* TODO FIXME XXX: error_mem */ 43cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes } 44cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes dict_enter(proc->breakpoints, addr, sbp); 45cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes sbp->addr = addr; 462d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand sbp->libsym = libsym; 472d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand if (libsym) 482d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand libsym->brkpnt = sbp; 495b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes } 5063184be8c577f5799e44db2a4e312a8240ad7751Juan Cespedes#ifdef __arm__ 5163184be8c577f5799e44db2a4e312a8240ad7751Juan Cespedes sbp->thumb_mode = proc->thumb_mode; 5263184be8c577f5799e44db2a4e312a8240ad7751Juan Cespedes proc->thumb_mode = 0; 5363184be8c577f5799e44db2a4e312a8240ad7751Juan Cespedes#endif 545b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes sbp->enabled++; 552d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand if (sbp->enabled == 1 && proc->pid) 562d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand enable_breakpoint(proc->pid, sbp); 575b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes} 585b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 59f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesvoid 60f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesdelete_breakpoint(struct process *proc, void *addr) { 612d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand struct breakpoint *sbp = dict_find_entry(proc->breakpoints, addr); 622d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand assert(sbp); /* FIXME: remove after debugging has been done. */ 635b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes /* This should only happen on out-of-memory conditions. */ 642d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand if (sbp == NULL) 652d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand return; 665b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 675b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes sbp->enabled--; 682d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand if (sbp->enabled == 0) 692d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand disable_breakpoint(proc->pid, sbp); 705b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes assert(sbp->enabled >= 0); 715b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes} 725b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 73f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesstatic void 74f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesenable_bp_cb(void *addr, void *sbp, void *proc) { 75cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes if (((struct breakpoint *)sbp)->enabled) { 76cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes enable_breakpoint(((struct process *)proc)->pid, sbp); 77cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes } 785b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes} 795b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 80f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesvoid 81f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesenable_all_breakpoints(struct process *proc) { 825e01f654d83a95f2acffa86df57a4c2db9b0cae9Juan Cespedes if (proc->breakpoints_enabled <= 0) { 83f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes#ifdef __powerpc__ 84f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes unsigned long a; 85f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes 86f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes /* 87f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes * PPC HACK! (XXX FIXME TODO) 88f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes * If the dynamic linker hasn't populated the PLT then 89f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes * dont enable the breakpoints 90f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes */ 91ce377d567ccc0b14693619b69ebe0ac6deb0ba90Juan Cespedes if (options.libcalls) { 922d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand a = ptrace(PTRACE_PEEKTEXT, proc->pid, 9376c61f15d7989bf7adffed2e46a44c34a80bd927Paul Gilliam sym2addr(proc, proc->list_of_symbols), 942d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand 0); 95de5a7eb873c05a698e4267b554e25124dc92e7f4Juan Cespedes if (a == 0x0) 96de5a7eb873c05a698e4267b554e25124dc92e7f4Juan Cespedes return; 97de5a7eb873c05a698e4267b554e25124dc92e7f4Juan Cespedes } 98f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes#endif 99f1bfe203f5f1c0e11a614f9d593a68406f5cb47eJuan Cespedes 100cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes debug(1, "Enabling breakpoints for pid %u...", proc->pid); 101a0ccf39a68c0fcdf2165bde0f9b70ed12fc61cd8Juan Cespedes if (proc->breakpoints) { 1022d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand dict_apply_to_all(proc->breakpoints, enable_bp_cb, 1032d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand proc); 104a0ccf39a68c0fcdf2165bde0f9b70ed12fc61cd8Juan Cespedes } 1051228a91e6560c5e5ac4fdd051adc9b34bf9fc047Eric Vaitl#ifdef __mips__ 106a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes { 107a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes // I'm sure there is a nicer way to do this. We need to 108a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes // insert breakpoints _after_ the child has been started. 109a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes struct library_symbol *sym; 110a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes struct library_symbol *new_sym; 111a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes sym=proc->list_of_symbols; 112a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes while(sym){ 113a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes void *addr= sym2addr(proc,sym); 114a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes if(!addr){ 115a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes sym=sym->next; 116a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes continue; 117a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes } 118a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes if(dict_find_entry(proc->breakpoints,addr)){ 119a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes sym=sym->next; 120a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes continue; 121a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes } 122a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes debug(2,"inserting bp %p %s",addr,sym->name); 123a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes new_sym=malloc(sizeof(*new_sym)); 124a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes memcpy(new_sym,sym,sizeof(*new_sym)); 125a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes new_sym->next=proc->list_of_symbols; 126a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes proc->list_of_symbols=new_sym; 127a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes new_sym->brkpnt=0; 128a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes insert_breakpoint(proc, addr, new_sym); 129a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes sym=sym->next; 130a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes } 131a413e5b8880de643a83ad124d078091c0956fe1dJuan Cespedes } 1321228a91e6560c5e5ac4fdd051adc9b34bf9fc047Eric Vaitl#endif 1335e01f654d83a95f2acffa86df57a4c2db9b0cae9Juan Cespedes } 1345e01f654d83a95f2acffa86df57a4c2db9b0cae9Juan Cespedes proc->breakpoints_enabled = 1; 1355e01f654d83a95f2acffa86df57a4c2db9b0cae9Juan Cespedes} 1365e01f654d83a95f2acffa86df57a4c2db9b0cae9Juan Cespedes 137f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesstatic void 138f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesdisable_bp_cb(void *addr, void *sbp, void *proc) { 139cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes if (((struct breakpoint *)sbp)->enabled) { 140cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes disable_breakpoint(((struct process *)proc)->pid, sbp); 141cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes } 1425b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes} 1435b3ffdf2e696273d38434ff7b3c26349fff5a0eaJuan Cespedes 144f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesvoid 145f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesdisable_all_breakpoints(struct process *proc) { 1465e01f654d83a95f2acffa86df57a4c2db9b0cae9Juan Cespedes if (proc->breakpoints_enabled) { 147cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes debug(1, "Disabling breakpoints for pid %u...", proc->pid); 148cac15c3f170b5ec2cc6304c8c0763a78103e1778Juan Cespedes dict_apply_to_all(proc->breakpoints, disable_bp_cb, proc); 1495e01f654d83a95f2acffa86df57a4c2db9b0cae9Juan Cespedes } 1505e01f654d83a95f2acffa86df57a4c2db9b0cae9Juan Cespedes proc->breakpoints_enabled = 0; 1515e01f654d83a95f2acffa86df57a4c2db9b0cae9Juan Cespedes} 1527186e2af704f4458e6383e8a92482594db29b597Juan Cespedes 153f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesstatic void 154f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesfree_bp_cb(void *addr, void *sbp, void *data) { 1557186e2af704f4458e6383e8a92482594db29b597Juan Cespedes assert(sbp); 1567186e2af704f4458e6383e8a92482594db29b597Juan Cespedes free(sbp); 1577186e2af704f4458e6383e8a92482594db29b597Juan Cespedes} 1587186e2af704f4458e6383e8a92482594db29b597Juan Cespedes 159f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesvoid 160f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesbreakpoints_init(struct process *proc) { 1612d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand struct library_symbol *sym; 1627186e2af704f4458e6383e8a92482594db29b597Juan Cespedes 1632d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand if (proc->breakpoints) { /* let's remove that struct */ 1647186e2af704f4458e6383e8a92482594db29b597Juan Cespedes dict_apply_to_all(proc->breakpoints, free_bp_cb, NULL); 1657186e2af704f4458e6383e8a92482594db29b597Juan Cespedes dict_clear(proc->breakpoints); 1667186e2af704f4458e6383e8a92482594db29b597Juan Cespedes proc->breakpoints = NULL; 1677186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } 16889a536028ab3b699b7f953b6b0fd7607917fc303Petr Machata proc->breakpoints = dict_init(dict_key2hash_int, dict_key_cmp_int); 1697186e2af704f4458e6383e8a92482594db29b597Juan Cespedes 170ce377d567ccc0b14693619b69ebe0ac6deb0ba90Juan Cespedes if (options.libcalls && proc->filename) { 1719a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand proc->list_of_symbols = read_elf(proc); 1727186e2af704f4458e6383e8a92482594db29b597Juan Cespedes if (opt_e) { 1732d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand struct library_symbol **tmp1 = &(proc->list_of_symbols); 1742d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand while (*tmp1) { 1752d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand struct opt_e_t *tmp2 = opt_e; 1767186e2af704f4458e6383e8a92482594db29b597Juan Cespedes int keep = !opt_e_enable; 1777186e2af704f4458e6383e8a92482594db29b597Juan Cespedes 1782d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand while (tmp2) { 1797186e2af704f4458e6383e8a92482594db29b597Juan Cespedes if (!strcmp((*tmp1)->name, tmp2->name)) { 1807186e2af704f4458e6383e8a92482594db29b597Juan Cespedes keep = opt_e_enable; 1817186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } 1827186e2af704f4458e6383e8a92482594db29b597Juan Cespedes tmp2 = tmp2->next; 1837186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } 1847186e2af704f4458e6383e8a92482594db29b597Juan Cespedes if (!keep) { 1857186e2af704f4458e6383e8a92482594db29b597Juan Cespedes *tmp1 = (*tmp1)->next; 1867186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } else { 1877186e2af704f4458e6383e8a92482594db29b597Juan Cespedes tmp1 = &((*tmp1)->next); 1887186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } 1897186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } 1907186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } 1917186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } else { 1927186e2af704f4458e6383e8a92482594db29b597Juan Cespedes proc->list_of_symbols = NULL; 1937186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } 194b3f8fef12fccb0914b7b28725f42192c279d31c9Petr Machata for (sym = proc->list_of_symbols; sym; sym = sym->next) { 1952d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand /* proc->pid==0 delays enabling. */ 19676c61f15d7989bf7adffed2e46a44c34a80bd927Paul Gilliam insert_breakpoint(proc, sym2addr(proc, sym), sym); 1977186e2af704f4458e6383e8a92482594db29b597Juan Cespedes } 1987186e2af704f4458e6383e8a92482594db29b597Juan Cespedes proc->callstack_depth = 0; 1997186e2af704f4458e6383e8a92482594db29b597Juan Cespedes proc->breakpoints_enabled = -1; 2007186e2af704f4458e6383e8a92482594db29b597Juan Cespedes} 2019a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand 202f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesvoid 203f13505251e6402460f6cc7ec84e0d8ca91607b4fJuan Cespedesreinitialize_breakpoints(struct process *proc) { 2042d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand struct library_symbol *sym = proc->list_of_symbols; 2052d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand 2062d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand while (sym) { 2079a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand if (sym->needs_init) { 20876c61f15d7989bf7adffed2e46a44c34a80bd927Paul Gilliam insert_breakpoint(proc, sym2addr(proc, sym), 2092d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand sym); 2102d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand if (sym->needs_init && !sym->is_weak) { 2112d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand fprintf(stderr, 2122d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand "could not re-initialize breakpoint for \"%s\" in file \"%s\"\n", 2132d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand sym->name, proc->filename); 2149a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand exit(1); 2152d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand } 2162d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand } 2179a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand sym = sym->next; 2182d45b1a8e26a36a9f85dc49e721c4390ca93dc40Ian Wienand } 2199a2ad351a1c3215dc596ff3e2e3fd4bc24445a6bIan Wienand} 220