1 Star 0 Fork 0

taylor / objc4

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
markgc.cpp 21.87 KB
一键复制 编辑 原始数据 按行查看 历史
Apple Open Source 提交于 2015-12-08 19:46 . Version 680
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
/*
* Copyright (c) 2007-2009 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <mach-o/fat.h>
#include <mach-o/arch.h>
#include <mach-o/loader.h>
// from "objc-private.h"
// masks for objc_image_info.flags
#define OBJC_IMAGE_SUPPORTS_GC (1<<1)
// Some OS X SDKs don't define these.
#ifndef CPU_TYPE_ARM
#define CPU_TYPE_ARM ((cpu_type_t) 12)
#endif
#ifndef CPU_ARCH_ABI64
#define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */
#endif
#ifndef CPU_TYPE_ARM64
#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)
#endif
// File abstraction taken from ld64/FileAbstraction.hpp
// and ld64/MachOFileAbstraction.hpp.
#ifdef __OPTIMIZE__
#define INLINE __attribute__((always_inline))
#else
#define INLINE
#endif
//
// This abstraction layer is for use with file formats that have 64-bit/32-bit and Big-Endian/Little-Endian variants
//
// For example: to make a utility that handles 32-bit little enidan files use: Pointer32<LittleEndian>
//
//
// get16() read a 16-bit number from an E endian struct
// set16() write a 16-bit number to an E endian struct
// get32() read a 32-bit number from an E endian struct
// set32() write a 32-bit number to an E endian struct
// get64() read a 64-bit number from an E endian struct
// set64() write a 64-bit number to an E endian struct
//
// getBits() read a bit field from an E endian struct (bitCount=number of bits in field, firstBit=bit index of field)
// setBits() write a bit field to an E endian struct (bitCount=number of bits in field, firstBit=bit index of field)
//
// getBitsRaw() read a bit field from a struct with native endianness
// setBitsRaw() write a bit field from a struct with native endianness
//
class BigEndian
{
public:
static uint16_t get16(const uint16_t& from) INLINE { return OSReadBigInt16(&from, 0); }
static void set16(uint16_t& into, uint16_t value) INLINE { OSWriteBigInt16(&into, 0, value); }
static uint32_t get32(const uint32_t& from) INLINE { return OSReadBigInt32(&from, 0); }
static void set32(uint32_t& into, uint32_t value) INLINE { OSWriteBigInt32(&into, 0, value); }
static uint64_t get64(const uint64_t& from) INLINE { return OSReadBigInt64(&from, 0); }
static void set64(uint64_t& into, uint64_t value) INLINE { OSWriteBigInt64(&into, 0, value); }
static uint32_t getBits(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return getBitsRaw(get32(from), firstBit, bitCount); }
static void setBits(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = get32(into); setBitsRaw(temp, value, firstBit, bitCount); set32(into, temp); }
static uint32_t getBitsRaw(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return ((from >> (32-firstBit-bitCount)) & ((1<<bitCount)-1)); }
static void setBitsRaw(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = into;
const uint32_t mask = ((1<<bitCount)-1);
temp &= ~(mask << (32-firstBit-bitCount));
temp |= ((value & mask) << (32-firstBit-bitCount));
into = temp; }
enum { little_endian = 0 };
};
class LittleEndian
{
public:
static uint16_t get16(const uint16_t& from) INLINE { return OSReadLittleInt16(&from, 0); }
static void set16(uint16_t& into, uint16_t value) INLINE { OSWriteLittleInt16(&into, 0, value); }
static uint32_t get32(const uint32_t& from) INLINE { return OSReadLittleInt32(&from, 0); }
static void set32(uint32_t& into, uint32_t value) INLINE { OSWriteLittleInt32(&into, 0, value); }
static uint64_t get64(const uint64_t& from) INLINE { return OSReadLittleInt64(&from, 0); }
static void set64(uint64_t& into, uint64_t value) INLINE { OSWriteLittleInt64(&into, 0, value); }
static uint32_t getBits(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return getBitsRaw(get32(from), firstBit, bitCount); }
static void setBits(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = get32(into); setBitsRaw(temp, value, firstBit, bitCount); set32(into, temp); }
static uint32_t getBitsRaw(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return ((from >> firstBit) & ((1<<bitCount)-1)); }
static void setBitsRaw(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = into;
const uint32_t mask = ((1<<bitCount)-1);
temp &= ~(mask << firstBit);
temp |= ((value & mask) << firstBit);
into = temp; }
enum { little_endian = 1 };
};
#if __BIG_ENDIAN__
typedef BigEndian CurrentEndian;
typedef LittleEndian OtherEndian;
#elif __LITTLE_ENDIAN__
typedef LittleEndian CurrentEndian;
typedef BigEndian OtherEndian;
#else
#error unknown endianness
#endif
template <typename _E>
class Pointer32
{
public:
typedef uint32_t uint_t;
typedef int32_t sint_t;
typedef _E E;
static uint64_t getP(const uint_t& from) INLINE { return _E::get32(from); }
static void setP(uint_t& into, uint64_t value) INLINE { _E::set32(into, value); }
};
template <typename _E>
class Pointer64
{
public:
typedef uint64_t uint_t;
typedef int64_t sint_t;
typedef _E E;
static uint64_t getP(const uint_t& from) INLINE { return _E::get64(from); }
static void setP(uint_t& into, uint64_t value) INLINE { _E::set64(into, value); }
};
//
// mach-o file header
//
template <typename P> struct macho_header_content {};
template <> struct macho_header_content<Pointer32<BigEndian> > { mach_header fields; };
template <> struct macho_header_content<Pointer64<BigEndian> > { mach_header_64 fields; };
template <> struct macho_header_content<Pointer32<LittleEndian> > { mach_header fields; };
template <> struct macho_header_content<Pointer64<LittleEndian> > { mach_header_64 fields; };
template <typename P>
class macho_header {
public:
uint32_t magic() const INLINE { return E::get32(header.fields.magic); }
void set_magic(uint32_t value) INLINE { E::set32(header.fields.magic, value); }
uint32_t cputype() const INLINE { return E::get32(header.fields.cputype); }
void set_cputype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cputype, value); }
uint32_t cpusubtype() const INLINE { return E::get32(header.fields.cpusubtype); }
void set_cpusubtype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cpusubtype, value); }
uint32_t filetype() const INLINE { return E::get32(header.fields.filetype); }
void set_filetype(uint32_t value) INLINE { E::set32(header.fields.filetype, value); }
uint32_t ncmds() const INLINE { return E::get32(header.fields.ncmds); }
void set_ncmds(uint32_t value) INLINE { E::set32(header.fields.ncmds, value); }
uint32_t sizeofcmds() const INLINE { return E::get32(header.fields.sizeofcmds); }
void set_sizeofcmds(uint32_t value) INLINE { E::set32(header.fields.sizeofcmds, value); }
uint32_t flags() const INLINE { return E::get32(header.fields.flags); }
void set_flags(uint32_t value) INLINE { E::set32(header.fields.flags, value); }
uint32_t reserved() const INLINE { return E::get32(header.fields.reserved); }
void set_reserved(uint32_t value) INLINE { E::set32(header.fields.reserved, value); }
typedef typename P::E E;
private:
macho_header_content<P> header;
};
//
// mach-o load command
//
template <typename P>
class macho_load_command {
public:
uint32_t cmd() const INLINE { return E::get32(command.cmd); }
void set_cmd(uint32_t value) INLINE { E::set32(command.cmd, value); }
uint32_t cmdsize() const INLINE { return E::get32(command.cmdsize); }
void set_cmdsize(uint32_t value) INLINE { E::set32(command.cmdsize, value); }
typedef typename P::E E;
private:
load_command command;
};
//
// mach-o segment load command
//
template <typename P> struct macho_segment_content {};
template <> struct macho_segment_content<Pointer32<BigEndian> > { segment_command fields; enum { CMD = LC_SEGMENT }; };
template <> struct macho_segment_content<Pointer64<BigEndian> > { segment_command_64 fields; enum { CMD = LC_SEGMENT_64 }; };
template <> struct macho_segment_content<Pointer32<LittleEndian> > { segment_command fields; enum { CMD = LC_SEGMENT }; };
template <> struct macho_segment_content<Pointer64<LittleEndian> > { segment_command_64 fields; enum { CMD = LC_SEGMENT_64 }; };
template <typename P>
class macho_segment_command {
public:
uint32_t cmd() const INLINE { return E::get32(segment.fields.cmd); }
void set_cmd(uint32_t value) INLINE { E::set32(segment.fields.cmd, value); }
uint32_t cmdsize() const INLINE { return E::get32(segment.fields.cmdsize); }
void set_cmdsize(uint32_t value) INLINE { E::set32(segment.fields.cmdsize, value); }
const char* segname() const INLINE { return segment.fields.segname; }
void set_segname(const char* value) INLINE { strncpy(segment.fields.segname, value, 16); }
uint64_t vmaddr() const INLINE { return P::getP(segment.fields.vmaddr); }
void set_vmaddr(uint64_t value) INLINE { P::setP(segment.fields.vmaddr, value); }
uint64_t vmsize() const INLINE { return P::getP(segment.fields.vmsize); }
void set_vmsize(uint64_t value) INLINE { P::setP(segment.fields.vmsize, value); }
uint64_t fileoff() const INLINE { return P::getP(segment.fields.fileoff); }
void set_fileoff(uint64_t value) INLINE { P::setP(segment.fields.fileoff, value); }
uint64_t filesize() const INLINE { return P::getP(segment.fields.filesize); }
void set_filesize(uint64_t value) INLINE { P::setP(segment.fields.filesize, value); }
uint32_t maxprot() const INLINE { return E::get32(segment.fields.maxprot); }
void set_maxprot(uint32_t value) INLINE { E::set32((uint32_t&)segment.fields.maxprot, value); }
uint32_t initprot() const INLINE { return E::get32(segment.fields.initprot); }
void set_initprot(uint32_t value) INLINE { E::set32((uint32_t&)segment.fields.initprot, value); }
uint32_t nsects() const INLINE { return E::get32(segment.fields.nsects); }
void set_nsects(uint32_t value) INLINE { E::set32(segment.fields.nsects, value); }
uint32_t flags() const INLINE { return E::get32(segment.fields.flags); }
void set_flags(uint32_t value) INLINE { E::set32(segment.fields.flags, value); }
enum {
CMD = macho_segment_content<P>::CMD
};
typedef typename P::E E;
private:
macho_segment_content<P> segment;
};
//
// mach-o section
//
template <typename P> struct macho_section_content {};
template <> struct macho_section_content<Pointer32<BigEndian> > { section fields; };
template <> struct macho_section_content<Pointer64<BigEndian> > { section_64 fields; };
template <> struct macho_section_content<Pointer32<LittleEndian> > { section fields; };
template <> struct macho_section_content<Pointer64<LittleEndian> > { section_64 fields; };
template <typename P>
class macho_section {
public:
const char* sectname() const INLINE { return section.fields.sectname; }
void set_sectname(const char* value) INLINE { strncpy(section.fields.sectname, value, 16); }
const char* segname() const INLINE { return section.fields.segname; }
void set_segname(const char* value) INLINE { strncpy(section.fields.segname, value, 16); }
uint64_t addr() const INLINE { return P::getP(section.fields.addr); }
void set_addr(uint64_t value) INLINE { P::setP(section.fields.addr, value); }
uint64_t size() const INLINE { return P::getP(section.fields.size); }
void set_size(uint64_t value) INLINE { P::setP(section.fields.size, value); }
uint32_t offset() const INLINE { return E::get32(section.fields.offset); }
void set_offset(uint32_t value) INLINE { E::set32(section.fields.offset, value); }
uint32_t align() const INLINE { return E::get32(section.fields.align); }
void set_align(uint32_t value) INLINE { E::set32(section.fields.align, value); }
uint32_t reloff() const INLINE { return E::get32(section.fields.reloff); }
void set_reloff(uint32_t value) INLINE { E::set32(section.fields.reloff, value); }
uint32_t nreloc() const INLINE { return E::get32(section.fields.nreloc); }
void set_nreloc(uint32_t value) INLINE { E::set32(section.fields.nreloc, value); }
uint32_t flags() const INLINE { return E::get32(section.fields.flags); }
void set_flags(uint32_t value) INLINE { E::set32(section.fields.flags, value); }
uint32_t reserved1() const INLINE { return E::get32(section.fields.reserved1); }
void set_reserved1(uint32_t value) INLINE { E::set32(section.fields.reserved1, value); }
uint32_t reserved2() const INLINE { return E::get32(section.fields.reserved2); }
void set_reserved2(uint32_t value) INLINE { E::set32(section.fields.reserved2, value); }
typedef typename P::E E;
private:
macho_section_content<P> section;
};
static bool debug = true;
bool processFile(const char *filename);
int main(int argc, const char *argv[]) {
for (int i = 1; i < argc; ++i) {
if (!processFile(argv[i])) return 1;
}
return 0;
}
struct imageinfo {
uint32_t version;
uint32_t flags;
};
// Segment and section names are 16 bytes and may be un-terminated.
bool segnameEquals(const char *lhs, const char *rhs)
{
return 0 == strncmp(lhs, rhs, 16);
}
bool segnameStartsWith(const char *segname, const char *prefix)
{
return 0 == strncmp(segname, prefix, strlen(prefix));
}
bool sectnameEquals(const char *lhs, const char *rhs)
{
return segnameEquals(lhs, rhs);
}
template <typename P>
void dosect(uint8_t *start, macho_section<P> *sect, bool isOldABI, bool isOSX)
{
if (debug) printf("section %.16s from segment %.16s\n",
sect->sectname(), sect->segname());
if (isOSX) {
// Add "supports GC" flag to objc image info
if ((segnameStartsWith(sect->segname(), "__DATA") &&
sectnameEquals(sect->sectname(), "__objc_imageinfo")) ||
(segnameEquals(sect->segname(), "__OBJC") &&
sectnameEquals(sect->sectname(), "__image_info")))
{
imageinfo *ii = (imageinfo*)(start + sect->offset());
P::E::set32(ii->flags, P::E::get32(ii->flags) | OBJC_IMAGE_SUPPORTS_GC);
if (debug) printf("added GC support flag\n");
}
}
if (isOldABI) {
// Keep init funcs because libSystem doesn't call _objc_init().
} else {
// Strip S_MOD_INIT/TERM_FUNC_POINTERS. We don't want dyld to call
// our init funcs because it is too late, and we don't want anyone to
// call our term funcs ever.
if (segnameStartsWith(sect->segname(), "__DATA") &&
sectnameEquals(sect->sectname(), "__mod_init_func"))
{
// section type 0 is S_REGULAR
sect->set_flags(sect->flags() & ~SECTION_TYPE);
sect->set_sectname("__objc_init_func");
if (debug) printf("disabled __mod_init_func section\n");
}
if (segnameStartsWith(sect->segname(), "__DATA") &&
sectnameEquals(sect->sectname(), "__mod_term_func"))
{
// section type 0 is S_REGULAR
sect->set_flags(sect->flags() & ~SECTION_TYPE);
sect->set_sectname("__objc_term_func");
if (debug) printf("disabled __mod_term_func section\n");
}
}
}
template <typename P>
void doseg(uint8_t *start, macho_segment_command<P> *seg,
bool isOldABI, bool isOSX)
{
if (debug) printf("segment name: %.16s, nsects %u\n",
seg->segname(), seg->nsects());
macho_section<P> *sect = (macho_section<P> *)(seg + 1);
for (uint32_t i = 0; i < seg->nsects(); ++i) {
dosect(start, &sect[i], isOldABI, isOSX);
}
}
template<typename P>
bool parse_macho(uint8_t *buffer)
{
macho_header<P>* mh = (macho_header<P>*)buffer;
uint8_t *cmds;
bool isOldABI = false;
bool isOSX = false;
cmds = (uint8_t *)(mh + 1);
for (uint32_t c = 0; c < mh->ncmds(); c++) {
macho_load_command<P>* cmd = (macho_load_command<P>*)cmds;
cmds += cmd->cmdsize();
if (cmd->cmd() == LC_SEGMENT || cmd->cmd() == LC_SEGMENT_64) {
macho_segment_command<P>* seg = (macho_segment_command<P>*)cmd;
if (segnameEquals(seg->segname(), "__OBJC")) isOldABI = true;
}
else if (cmd->cmd() == LC_VERSION_MIN_MACOSX) {
isOSX = true;
}
}
if (debug) printf("ABI=%s, OS=%s\n",
isOldABI ? "old" : "new", isOSX ? "osx" : "ios");
cmds = (uint8_t *)(mh + 1);
for (uint32_t c = 0; c < mh->ncmds(); c++) {
macho_load_command<P>* cmd = (macho_load_command<P>*)cmds;
cmds += cmd->cmdsize();
if (cmd->cmd() == LC_SEGMENT || cmd->cmd() == LC_SEGMENT_64) {
doseg(buffer, (macho_segment_command<P>*)cmd, isOldABI, isOSX);
}
}
return true;
}
bool parse_macho(uint8_t *buffer)
{
uint32_t magic = *(uint32_t *)buffer;
switch (magic) {
case MH_MAGIC_64:
return parse_macho<Pointer64<CurrentEndian>>(buffer);
case MH_MAGIC:
return parse_macho<Pointer32<CurrentEndian>>(buffer);
case MH_CIGAM_64:
return parse_macho<Pointer64<OtherEndian>>(buffer);
case MH_CIGAM:
return parse_macho<Pointer32<OtherEndian>>(buffer);
default:
printf("file is not mach-o (magic %x)\n", magic);
return false;
}
}
bool parse_fat(uint8_t *buffer, size_t size)
{
uint32_t magic;
if (size < sizeof(magic)) {
printf("file is too small\n");
return false;
}
magic = *(uint32_t *)buffer;
if (magic != FAT_MAGIC && magic != FAT_CIGAM) {
/* Not a fat file */
return parse_macho(buffer);
} else {
struct fat_header *fh;
uint32_t fat_magic, fat_nfat_arch;
struct fat_arch *archs;
if (size < sizeof(struct fat_header)) {
printf("file is too small\n");
return false;
}
fh = (struct fat_header *)buffer;
fat_magic = OSSwapBigToHostInt32(fh->magic);
fat_nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
if (size < (sizeof(struct fat_header) + fat_nfat_arch * sizeof(struct fat_arch))) {
printf("file is too small\n");
return false;
}
archs = (struct fat_arch *)(buffer + sizeof(struct fat_header));
/* Special case hidden CPU_TYPE_ARM64 */
if (size >= (sizeof(struct fat_header) + (fat_nfat_arch + 1) * sizeof(struct fat_arch))) {
if (fat_nfat_arch > 0
&& OSSwapBigToHostInt32(archs[fat_nfat_arch].cputype) == CPU_TYPE_ARM64) {
fat_nfat_arch++;
}
}
/* End special case hidden CPU_TYPE_ARM64 */
if (debug) printf("%d fat architectures\n",
fat_nfat_arch);
for (uint32_t i = 0; i < fat_nfat_arch; i++) {
uint32_t arch_cputype = OSSwapBigToHostInt32(archs[i].cputype);
uint32_t arch_cpusubtype = OSSwapBigToHostInt32(archs[i].cpusubtype);
uint32_t arch_offset = OSSwapBigToHostInt32(archs[i].offset);
uint32_t arch_size = OSSwapBigToHostInt32(archs[i].size);
if (debug) printf("cputype %d cpusubtype %d\n",
arch_cputype, arch_cpusubtype);
/* Check that slice data is after all fat headers and archs */
if (arch_offset < (sizeof(struct fat_header) + fat_nfat_arch * sizeof(struct fat_arch))) {
printf("file is badly formed\n");
return false;
}
/* Check that the slice ends before the file does */
if (arch_offset > size) {
printf("file is badly formed\n");
return false;
}
if (arch_size > size) {
printf("file is badly formed\n");
return false;
}
if (arch_offset > (size - arch_size)) {
printf("file is badly formed\n");
return false;
}
bool ok = parse_macho(buffer + arch_offset);
if (!ok) return false;
}
return true;
}
}
bool processFile(const char *filename)
{
if (debug) printf("file %s\n", filename);
int fd = open(filename, O_RDWR);
if (fd < 0) {
printf("open %s: %s\n", filename, strerror(errno));
return false;
}
struct stat st;
if (fstat(fd, &st) < 0) {
printf("fstat %s: %s\n", filename, strerror(errno));
return false;
}
void *buffer = mmap(NULL, (size_t)st.st_size, PROT_READ|PROT_WRITE,
MAP_FILE|MAP_SHARED, fd, 0);
if (buffer == MAP_FAILED) {
printf("mmap %s: %s\n", filename, strerror(errno));
return false;
}
bool result = parse_fat((uint8_t *)buffer, (size_t)st.st_size);
munmap(buffer, (size_t)st.st_size);
close(fd);
return result;
}
1
https://gitee.com/taylor_bush/objc4.git
git@gitee.com:taylor_bush/objc4.git
taylor_bush
objc4
objc4
master

搜索帮助