| 1 | use alloc::vec::Vec; | 
| 2 | use core::fmt::Debug; | 
| 3 | use core::{fmt, slice, str}; | 
| 4 |  | 
| 5 | use crate::endian::{self, Endianness}; | 
| 6 | use crate::macho; | 
| 7 | use crate::pod::Pod; | 
| 8 | use crate::read::util::StringTable; | 
| 9 | use crate::read::{ | 
| 10 |     self, ObjectMap, ObjectMapEntry, ObjectSymbol, ObjectSymbolTable, ReadError, ReadRef, Result, | 
| 11 |     SectionIndex, SectionKind, SymbolFlags, SymbolIndex, SymbolKind, SymbolMap, SymbolMapEntry, | 
| 12 |     SymbolScope, SymbolSection, | 
| 13 | }; | 
| 14 |  | 
| 15 | use super::{MachHeader, MachOFile}; | 
| 16 |  | 
| 17 | /// A table of symbol entries in a Mach-O file. | 
| 18 | /// | 
| 19 | /// Also includes the string table used for the symbol names. | 
| 20 | /// | 
| 21 | /// Returned by [`macho::SymtabCommand::symbols`]. | 
| 22 | #[derive (Debug, Clone, Copy)] | 
| 23 | pub struct SymbolTable<'data, Mach: MachHeader, R = &'data [u8]> | 
| 24 | where | 
| 25 |     R: ReadRef<'data>, | 
| 26 | { | 
| 27 |     symbols: &'data [Mach::Nlist], | 
| 28 |     strings: StringTable<'data, R>, | 
| 29 | } | 
| 30 |  | 
| 31 | impl<'data, Mach: MachHeader, R: ReadRef<'data>> Default for SymbolTable<'data, Mach, R> { | 
| 32 |     fn default() -> Self { | 
| 33 |         SymbolTable { | 
| 34 |             symbols: &[], | 
| 35 |             strings: Default::default(), | 
| 36 |         } | 
| 37 |     } | 
| 38 | } | 
| 39 |  | 
| 40 | impl<'data, Mach: MachHeader, R: ReadRef<'data>> SymbolTable<'data, Mach, R> { | 
| 41 |     #[inline ] | 
| 42 |     pub(super) fn new(symbols: &'data [Mach::Nlist], strings: StringTable<'data, R>) -> Self { | 
| 43 |         SymbolTable { symbols, strings } | 
| 44 |     } | 
| 45 |  | 
| 46 |     /// Return the string table used for the symbol names. | 
| 47 |     #[inline ] | 
| 48 |     pub fn strings(&self) -> StringTable<'data, R> { | 
| 49 |         self.strings | 
| 50 |     } | 
| 51 |  | 
| 52 |     /// Iterate over the symbols. | 
| 53 |     #[inline ] | 
| 54 |     pub fn iter(&self) -> slice::Iter<'data, Mach::Nlist> { | 
| 55 |         self.symbols.iter() | 
| 56 |     } | 
| 57 |  | 
| 58 |     /// Return true if the symbol table is empty. | 
| 59 |     #[inline ] | 
| 60 |     pub fn is_empty(&self) -> bool { | 
| 61 |         self.symbols.is_empty() | 
| 62 |     } | 
| 63 |  | 
| 64 |     /// The number of symbols. | 
| 65 |     #[inline ] | 
| 66 |     pub fn len(&self) -> usize { | 
| 67 |         self.symbols.len() | 
| 68 |     } | 
| 69 |  | 
| 70 |     /// Return the symbol at the given index. | 
| 71 |     pub fn symbol(&self, index: usize) -> Result<&'data Mach::Nlist> { | 
| 72 |         self.symbols | 
| 73 |             .get(index) | 
| 74 |             .read_error("Invalid Mach-O symbol index" ) | 
| 75 |     } | 
| 76 |  | 
| 77 |     /// Construct a map from addresses to a user-defined map entry. | 
| 78 |     pub fn map<Entry: SymbolMapEntry, F: Fn(&'data Mach::Nlist) -> Option<Entry>>( | 
| 79 |         &self, | 
| 80 |         f: F, | 
| 81 |     ) -> SymbolMap<Entry> { | 
| 82 |         let mut symbols = Vec::new(); | 
| 83 |         for nlist in self.symbols { | 
| 84 |             if !nlist.is_definition() { | 
| 85 |                 continue; | 
| 86 |             } | 
| 87 |             if let Some(entry) = f(nlist) { | 
| 88 |                 symbols.push(entry); | 
| 89 |             } | 
| 90 |         } | 
| 91 |         SymbolMap::new(symbols) | 
| 92 |     } | 
| 93 |  | 
| 94 |     /// Construct a map from addresses to symbol names and object file names. | 
| 95 |     pub fn object_map(&self, endian: Mach::Endian) -> ObjectMap<'data> { | 
| 96 |         let mut symbols = Vec::new(); | 
| 97 |         let mut objects = Vec::new(); | 
| 98 |         let mut object = None; | 
| 99 |         let mut current_function = None; | 
| 100 |         // Each module starts with one or two N_SO symbols (path, or directory + filename) | 
| 101 |         // and one N_OSO symbol. The module is terminated by an empty N_SO symbol. | 
| 102 |         for nlist in self.symbols { | 
| 103 |             let n_type = nlist.n_type(); | 
| 104 |             if n_type & macho::N_STAB == 0 { | 
| 105 |                 continue; | 
| 106 |             } | 
| 107 |             // TODO: includes variables too (N_GSYM, N_STSYM). These may need to get their | 
| 108 |             // address from regular symbols though. | 
| 109 |             match n_type { | 
| 110 |                 macho::N_SO => { | 
| 111 |                     object = None; | 
| 112 |                 } | 
| 113 |                 macho::N_OSO => { | 
| 114 |                     object = None; | 
| 115 |                     if let Ok(name) = nlist.name(endian, self.strings) { | 
| 116 |                         if !name.is_empty() { | 
| 117 |                             object = Some(objects.len()); | 
| 118 |                             objects.push(name); | 
| 119 |                         } | 
| 120 |                     } | 
| 121 |                 } | 
| 122 |                 macho::N_FUN => { | 
| 123 |                     if let Ok(name) = nlist.name(endian, self.strings) { | 
| 124 |                         if !name.is_empty() { | 
| 125 |                             current_function = Some((name, nlist.n_value(endian).into())) | 
| 126 |                         } else if let Some((name, address)) = current_function.take() { | 
| 127 |                             if let Some(object) = object { | 
| 128 |                                 symbols.push(ObjectMapEntry { | 
| 129 |                                     address, | 
| 130 |                                     size: nlist.n_value(endian).into(), | 
| 131 |                                     name, | 
| 132 |                                     object, | 
| 133 |                                 }); | 
| 134 |                             } | 
| 135 |                         } | 
| 136 |                     } | 
| 137 |                 } | 
| 138 |                 _ => {} | 
| 139 |             } | 
| 140 |         } | 
| 141 |         ObjectMap { | 
| 142 |             symbols: SymbolMap::new(symbols), | 
| 143 |             objects, | 
| 144 |         } | 
| 145 |     } | 
| 146 | } | 
| 147 |  | 
| 148 | /// A symbol table in a [`MachOFile32`](super::MachOFile32). | 
| 149 | pub type MachOSymbolTable32<'data, 'file, Endian = Endianness, R = &'data [u8]> = | 
| 150 |     MachOSymbolTable<'data, 'file, macho::MachHeader32<Endian>, R>; | 
| 151 | /// A symbol table in a [`MachOFile64`](super::MachOFile64). | 
| 152 | pub type MachOSymbolTable64<'data, 'file, Endian = Endianness, R = &'data [u8]> = | 
| 153 |     MachOSymbolTable<'data, 'file, macho::MachHeader64<Endian>, R>; | 
| 154 |  | 
| 155 | /// A symbol table in a [`MachOFile`]. | 
| 156 | #[derive (Debug, Clone, Copy)] | 
| 157 | pub struct MachOSymbolTable<'data, 'file, Mach, R = &'data [u8]> | 
| 158 | where | 
| 159 |     Mach: MachHeader, | 
| 160 |     R: ReadRef<'data>, | 
| 161 | { | 
| 162 |     pub(super) file: &'file MachOFile<'data, Mach, R>, | 
| 163 | } | 
| 164 |  | 
| 165 | impl<'data, 'file, Mach, R> read::private::Sealed for MachOSymbolTable<'data, 'file, Mach, R> | 
| 166 | where | 
| 167 |     Mach: MachHeader, | 
| 168 |     R: ReadRef<'data>, | 
| 169 | { | 
| 170 | } | 
| 171 |  | 
| 172 | impl<'data, 'file, Mach, R> ObjectSymbolTable<'data> for MachOSymbolTable<'data, 'file, Mach, R> | 
| 173 | where | 
| 174 |     Mach: MachHeader, | 
| 175 |     R: ReadRef<'data>, | 
| 176 | { | 
| 177 |     type Symbol = MachOSymbol<'data, 'file, Mach, R>; | 
| 178 |     type SymbolIterator = MachOSymbolIterator<'data, 'file, Mach, R>; | 
| 179 |  | 
| 180 |     fn symbols(&self) -> Self::SymbolIterator { | 
| 181 |         MachOSymbolIterator { | 
| 182 |             file: self.file, | 
| 183 |             index: 0, | 
| 184 |         } | 
| 185 |     } | 
| 186 |  | 
| 187 |     fn symbol_by_index(&self, index: SymbolIndex) -> Result<Self::Symbol> { | 
| 188 |         let nlist: &'data ::Nlist = self.file.symbols.symbol(index.0)?; | 
| 189 |         MachOSymbol::new(self.file, index, nlist).read_error("Unsupported Mach-O symbol index" ) | 
| 190 |     } | 
| 191 | } | 
| 192 |  | 
| 193 | /// An iterator for the symbols in a [`MachOFile32`](super::MachOFile32). | 
| 194 | pub type MachOSymbolIterator32<'data, 'file, Endian = Endianness, R = &'data [u8]> = | 
| 195 |     MachOSymbolIterator<'data, 'file, macho::MachHeader32<Endian>, R>; | 
| 196 | /// An iterator for the symbols in a [`MachOFile64`](super::MachOFile64). | 
| 197 | pub type MachOSymbolIterator64<'data, 'file, Endian = Endianness, R = &'data [u8]> = | 
| 198 |     MachOSymbolIterator<'data, 'file, macho::MachHeader64<Endian>, R>; | 
| 199 |  | 
| 200 | /// An iterator for the symbols in a [`MachOFile`]. | 
| 201 | pub struct MachOSymbolIterator<'data, 'file, Mach, R = &'data [u8]> | 
| 202 | where | 
| 203 |     Mach: MachHeader, | 
| 204 |     R: ReadRef<'data>, | 
| 205 | { | 
| 206 |     pub(super) file: &'file MachOFile<'data, Mach, R>, | 
| 207 |     pub(super) index: usize, | 
| 208 | } | 
| 209 |  | 
| 210 | impl<'data, 'file, Mach, R> fmt::Debug for MachOSymbolIterator<'data, 'file, Mach, R> | 
| 211 | where | 
| 212 |     Mach: MachHeader, | 
| 213 |     R: ReadRef<'data>, | 
| 214 | { | 
| 215 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
| 216 |         f.debug_struct(name:"MachOSymbolIterator" ).finish() | 
| 217 |     } | 
| 218 | } | 
| 219 |  | 
| 220 | impl<'data, 'file, Mach, R> Iterator for MachOSymbolIterator<'data, 'file, Mach, R> | 
| 221 | where | 
| 222 |     Mach: MachHeader, | 
| 223 |     R: ReadRef<'data>, | 
| 224 | { | 
| 225 |     type Item = MachOSymbol<'data, 'file, Mach, R>; | 
| 226 |  | 
| 227 |     fn next(&mut self) -> Option<Self::Item> { | 
| 228 |         loop { | 
| 229 |             let index: usize = self.index; | 
| 230 |             let nlist: &::Nlist = self.file.symbols.symbols.get(index)?; | 
| 231 |             self.index += 1; | 
| 232 |             if let Some(symbol: MachOSymbol<'_, '_, Mach, …>) = MachOSymbol::new(self.file, index:SymbolIndex(index), nlist) { | 
| 233 |                 return Some(symbol); | 
| 234 |             } | 
| 235 |         } | 
| 236 |     } | 
| 237 | } | 
| 238 |  | 
| 239 | /// A symbol in a [`MachOFile32`](super::MachOFile32). | 
| 240 | pub type MachOSymbol32<'data, 'file, Endian = Endianness, R = &'data [u8]> = | 
| 241 |     MachOSymbol<'data, 'file, macho::MachHeader32<Endian>, R>; | 
| 242 | /// A symbol in a [`MachOFile64`](super::MachOFile64). | 
| 243 | pub type MachOSymbol64<'data, 'file, Endian = Endianness, R = &'data [u8]> = | 
| 244 |     MachOSymbol<'data, 'file, macho::MachHeader64<Endian>, R>; | 
| 245 |  | 
| 246 | /// A symbol in a [`MachOFile`]. | 
| 247 | /// | 
| 248 | /// Most functionality is provided by the [`ObjectSymbol`] trait implementation. | 
| 249 | #[derive (Debug, Clone, Copy)] | 
| 250 | pub struct MachOSymbol<'data, 'file, Mach, R = &'data [u8]> | 
| 251 | where | 
| 252 |     Mach: MachHeader, | 
| 253 |     R: ReadRef<'data>, | 
| 254 | { | 
| 255 |     file: &'file MachOFile<'data, Mach, R>, | 
| 256 |     index: SymbolIndex, | 
| 257 |     nlist: &'data Mach::Nlist, | 
| 258 | } | 
| 259 |  | 
| 260 | impl<'data, 'file, Mach, R> MachOSymbol<'data, 'file, Mach, R> | 
| 261 | where | 
| 262 |     Mach: MachHeader, | 
| 263 |     R: ReadRef<'data>, | 
| 264 | { | 
| 265 |     pub(super) fn new( | 
| 266 |         file: &'file MachOFile<'data, Mach, R>, | 
| 267 |         index: SymbolIndex, | 
| 268 |         nlist: &'data Mach::Nlist, | 
| 269 |     ) -> Option<Self> { | 
| 270 |         if nlist.n_type() & macho::N_STAB != 0 { | 
| 271 |             return None; | 
| 272 |         } | 
| 273 |         Some(MachOSymbol { file, index, nlist }) | 
| 274 |     } | 
| 275 | } | 
| 276 |  | 
| 277 | impl<'data, 'file, Mach, R> read::private::Sealed for MachOSymbol<'data, 'file, Mach, R> | 
| 278 | where | 
| 279 |     Mach: MachHeader, | 
| 280 |     R: ReadRef<'data>, | 
| 281 | { | 
| 282 | } | 
| 283 |  | 
| 284 | impl<'data, 'file, Mach, R> ObjectSymbol<'data> for MachOSymbol<'data, 'file, Mach, R> | 
| 285 | where | 
| 286 |     Mach: MachHeader, | 
| 287 |     R: ReadRef<'data>, | 
| 288 | { | 
| 289 |     #[inline ] | 
| 290 |     fn index(&self) -> SymbolIndex { | 
| 291 |         self.index | 
| 292 |     } | 
| 293 |  | 
| 294 |     fn name_bytes(&self) -> Result<&'data [u8]> { | 
| 295 |         self.nlist.name(self.file.endian, self.file.symbols.strings) | 
| 296 |     } | 
| 297 |  | 
| 298 |     fn name(&self) -> Result<&'data str> { | 
| 299 |         let name = self.name_bytes()?; | 
| 300 |         str::from_utf8(name) | 
| 301 |             .ok() | 
| 302 |             .read_error("Non UTF-8 Mach-O symbol name" ) | 
| 303 |     } | 
| 304 |  | 
| 305 |     #[inline ] | 
| 306 |     fn address(&self) -> u64 { | 
| 307 |         self.nlist.n_value(self.file.endian).into() | 
| 308 |     } | 
| 309 |  | 
| 310 |     #[inline ] | 
| 311 |     fn size(&self) -> u64 { | 
| 312 |         0 | 
| 313 |     } | 
| 314 |  | 
| 315 |     fn kind(&self) -> SymbolKind { | 
| 316 |         self.section() | 
| 317 |             .index() | 
| 318 |             .and_then(|index| self.file.section_internal(index).ok()) | 
| 319 |             .map(|section| match section.kind { | 
| 320 |                 SectionKind::Text => SymbolKind::Text, | 
| 321 |                 SectionKind::Data | 
| 322 |                 | SectionKind::ReadOnlyData | 
| 323 |                 | SectionKind::ReadOnlyString | 
| 324 |                 | SectionKind::UninitializedData | 
| 325 |                 | SectionKind::Common => SymbolKind::Data, | 
| 326 |                 SectionKind::Tls | SectionKind::UninitializedTls | SectionKind::TlsVariables => { | 
| 327 |                     SymbolKind::Tls | 
| 328 |                 } | 
| 329 |                 _ => SymbolKind::Unknown, | 
| 330 |             }) | 
| 331 |             .unwrap_or(SymbolKind::Unknown) | 
| 332 |     } | 
| 333 |  | 
| 334 |     fn section(&self) -> SymbolSection { | 
| 335 |         match self.nlist.n_type() & macho::N_TYPE { | 
| 336 |             macho::N_UNDF => SymbolSection::Undefined, | 
| 337 |             macho::N_ABS => SymbolSection::Absolute, | 
| 338 |             macho::N_SECT => { | 
| 339 |                 let n_sect = self.nlist.n_sect(); | 
| 340 |                 if n_sect != 0 { | 
| 341 |                     SymbolSection::Section(SectionIndex(n_sect as usize)) | 
| 342 |                 } else { | 
| 343 |                     SymbolSection::Unknown | 
| 344 |                 } | 
| 345 |             } | 
| 346 |             _ => SymbolSection::Unknown, | 
| 347 |         } | 
| 348 |     } | 
| 349 |  | 
| 350 |     #[inline ] | 
| 351 |     fn is_undefined(&self) -> bool { | 
| 352 |         self.nlist.n_type() & macho::N_TYPE == macho::N_UNDF | 
| 353 |     } | 
| 354 |  | 
| 355 |     #[inline ] | 
| 356 |     fn is_definition(&self) -> bool { | 
| 357 |         self.nlist.is_definition() | 
| 358 |     } | 
| 359 |  | 
| 360 |     #[inline ] | 
| 361 |     fn is_common(&self) -> bool { | 
| 362 |         // Mach-O common symbols are based on section, not symbol | 
| 363 |         false | 
| 364 |     } | 
| 365 |  | 
| 366 |     #[inline ] | 
| 367 |     fn is_weak(&self) -> bool { | 
| 368 |         self.nlist.n_desc(self.file.endian) & (macho::N_WEAK_REF | macho::N_WEAK_DEF) != 0 | 
| 369 |     } | 
| 370 |  | 
| 371 |     fn scope(&self) -> SymbolScope { | 
| 372 |         let n_type = self.nlist.n_type(); | 
| 373 |         if n_type & macho::N_TYPE == macho::N_UNDF { | 
| 374 |             SymbolScope::Unknown | 
| 375 |         } else if n_type & macho::N_EXT == 0 { | 
| 376 |             SymbolScope::Compilation | 
| 377 |         } else if n_type & macho::N_PEXT != 0 { | 
| 378 |             SymbolScope::Linkage | 
| 379 |         } else { | 
| 380 |             SymbolScope::Dynamic | 
| 381 |         } | 
| 382 |     } | 
| 383 |  | 
| 384 |     #[inline ] | 
| 385 |     fn is_global(&self) -> bool { | 
| 386 |         self.scope() != SymbolScope::Compilation | 
| 387 |     } | 
| 388 |  | 
| 389 |     #[inline ] | 
| 390 |     fn is_local(&self) -> bool { | 
| 391 |         self.scope() == SymbolScope::Compilation | 
| 392 |     } | 
| 393 |  | 
| 394 |     #[inline ] | 
| 395 |     fn flags(&self) -> SymbolFlags<SectionIndex, SymbolIndex> { | 
| 396 |         let n_desc = self.nlist.n_desc(self.file.endian); | 
| 397 |         SymbolFlags::MachO { n_desc } | 
| 398 |     } | 
| 399 | } | 
| 400 |  | 
| 401 | /// A trait for generic access to [`macho::Nlist32`] and [`macho::Nlist64`]. | 
| 402 | #[allow (missing_docs)] | 
| 403 | pub trait Nlist: Debug + Pod { | 
| 404 |     type Word: Into<u64>; | 
| 405 |     type Endian: endian::Endian; | 
| 406 |  | 
| 407 |     fn n_strx(&self, endian: Self::Endian) -> u32; | 
| 408 |     fn n_type(&self) -> u8; | 
| 409 |     fn n_sect(&self) -> u8; | 
| 410 |     fn n_desc(&self, endian: Self::Endian) -> u16; | 
| 411 |     fn n_value(&self, endian: Self::Endian) -> Self::Word; | 
| 412 |  | 
| 413 |     fn name<'data, R: ReadRef<'data>>( | 
| 414 |         &self, | 
| 415 |         endian: Self::Endian, | 
| 416 |         strings: StringTable<'data, R>, | 
| 417 |     ) -> Result<&'data [u8]> { | 
| 418 |         strings | 
| 419 |             .get(self.n_strx(endian)) | 
| 420 |             .read_error("Invalid Mach-O symbol name offset" ) | 
| 421 |     } | 
| 422 |  | 
| 423 |     /// Return true if this is a STAB symbol. | 
| 424 |     /// | 
| 425 |     /// This determines the meaning of the `n_type` field. | 
| 426 |     fn is_stab(&self) -> bool { | 
| 427 |         self.n_type() & macho::N_STAB != 0 | 
| 428 |     } | 
| 429 |  | 
| 430 |     /// Return true if this is an undefined symbol. | 
| 431 |     fn is_undefined(&self) -> bool { | 
| 432 |         let n_type = self.n_type(); | 
| 433 |         n_type & macho::N_STAB == 0 && n_type & macho::N_TYPE == macho::N_UNDF | 
| 434 |     } | 
| 435 |  | 
| 436 |     /// Return true if the symbol is a definition of a function or data object. | 
| 437 |     fn is_definition(&self) -> bool { | 
| 438 |         let n_type = self.n_type(); | 
| 439 |         n_type & macho::N_STAB == 0 && n_type & macho::N_TYPE == macho::N_SECT | 
| 440 |     } | 
| 441 |  | 
| 442 |     /// Return the library ordinal. | 
| 443 |     /// | 
| 444 |     /// This is either a 1-based index into the dylib load commands, | 
| 445 |     /// or a special ordinal. | 
| 446 |     #[inline ] | 
| 447 |     fn library_ordinal(&self, endian: Self::Endian) -> u8 { | 
| 448 |         (self.n_desc(endian) >> 8) as u8 | 
| 449 |     } | 
| 450 | } | 
| 451 |  | 
| 452 | impl<Endian: endian::Endian> Nlist for macho::Nlist32<Endian> { | 
| 453 |     type Word = u32; | 
| 454 |     type Endian = Endian; | 
| 455 |  | 
| 456 |     fn n_strx(&self, endian: Self::Endian) -> u32 { | 
| 457 |         self.n_strx.get(endian) | 
| 458 |     } | 
| 459 |     fn n_type(&self) -> u8 { | 
| 460 |         self.n_type | 
| 461 |     } | 
| 462 |     fn n_sect(&self) -> u8 { | 
| 463 |         self.n_sect | 
| 464 |     } | 
| 465 |     fn n_desc(&self, endian: Self::Endian) -> u16 { | 
| 466 |         self.n_desc.get(endian) | 
| 467 |     } | 
| 468 |     fn n_value(&self, endian: Self::Endian) -> Self::Word { | 
| 469 |         self.n_value.get(endian) | 
| 470 |     } | 
| 471 | } | 
| 472 |  | 
| 473 | impl<Endian: endian::Endian> Nlist for macho::Nlist64<Endian> { | 
| 474 |     type Word = u64; | 
| 475 |     type Endian = Endian; | 
| 476 |  | 
| 477 |     fn n_strx(&self, endian: Self::Endian) -> u32 { | 
| 478 |         self.n_strx.get(endian) | 
| 479 |     } | 
| 480 |     fn n_type(&self) -> u8 { | 
| 481 |         self.n_type | 
| 482 |     } | 
| 483 |     fn n_sect(&self) -> u8 { | 
| 484 |         self.n_sect | 
| 485 |     } | 
| 486 |     fn n_desc(&self, endian: Self::Endian) -> u16 { | 
| 487 |         self.n_desc.get(endian) | 
| 488 |     } | 
| 489 |     fn n_value(&self, endian: Self::Endian) -> Self::Word { | 
| 490 |         self.n_value.get(endian) | 
| 491 |     } | 
| 492 | } | 
| 493 |  |