Skip to content

Commit

Permalink
8276769: -Xshare:auto should tolerate problems in the CDS archive
Browse files Browse the repository at this point in the history
Reviewed-by: iklam, ccheung
  • Loading branch information
yminqi committed Dec 8, 2021
1 parent 79165b7 commit 3e93e0b
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 135 deletions.
228 changes: 146 additions & 82 deletions src/hotspot/share/cds/filemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ void FileMapInfo::fail_continue(const char *msg, ...) {
fail_exit(msg, ap);
} else {
if (log_is_enabled(Info, cds)) {
ResourceMark rm;
LogStream ls(Log(cds)::info());
ls.print("UseSharedSpaces: ");
ls.vprint_cr(msg, ap);
Expand Down Expand Up @@ -1042,15 +1041,20 @@ void FileMapInfo::validate_non_existent_class_paths() {
}
}

// a utility class for checking file header
// A utility class for reading/validating the GenericCDSFileMapHeader portion of
// a CDS archive's header. The file header of all CDS archives with versions from
// CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION (12) are guaranteed to always start
// with GenericCDSFileMapHeader. This makes it possible to read important information
// from a CDS archive created by a different version of HotSpot, so that we can
// automatically regenerate the archive as necessary (JDK-8261455).
class FileHeaderHelper {
int _fd;
GenericCDSFileMapHeader _header;
bool _is_valid;
GenericCDSFileMapHeader* _header;
const char* _base_archive_name;

public:
FileHeaderHelper() {
_fd = -1;
}
FileHeaderHelper() : _fd(-1), _is_valid(false), _header(nullptr), _base_archive_name(nullptr) {}

~FileHeaderHelper() {
if (_fd != -1) {
Expand All @@ -1059,8 +1063,10 @@ class FileHeaderHelper {
}

bool initialize(const char* archive_name) {
log_info(cds)("Opening shared archive: %s", archive_name);
_fd = os::open(archive_name, O_RDONLY | O_BINARY, 0);
if (_fd < 0) {
FileMapInfo::fail_continue("Specified shared archive not found (%s)", archive_name);
return false;
}
return initialize(_fd);
Expand All @@ -1069,117 +1075,185 @@ class FileHeaderHelper {
// for an already opened file, do not set _fd
bool initialize(int fd) {
assert(fd != -1, "Archive should be opened");


// First read the generic header so we know the exact size of the actual header.
GenericCDSFileMapHeader gen_header;
size_t size = sizeof(GenericCDSFileMapHeader);
lseek(fd, 0, SEEK_SET);
size_t n = os::read(fd, (void*)&_header, (unsigned int)size);
size_t n = os::read(fd, (void*)&gen_header, (unsigned int)size);
if (n != size) {
vm_exit_during_initialization("Unable to read generic CDS file map header from shared archive");
FileMapInfo::fail_continue("Unable to read generic CDS file map header from shared archive");
return false;
}

if (gen_header._magic != CDS_ARCHIVE_MAGIC &&
gen_header._magic != CDS_DYNAMIC_ARCHIVE_MAGIC) {
FileMapInfo::fail_continue("The shared archive file has a bad magic number: %#x", gen_header._magic);
return false;
}

if (gen_header._version < CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION) {
FileMapInfo::fail_continue("Cannot handle shared archive file version %d. Must be at least %d",
gen_header._version, CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION);
return false;
}

size_t filelen = os::lseek(fd, 0, SEEK_END);
if (gen_header._header_size >= filelen) {
FileMapInfo::fail_continue("Archive file header larger than archive file");
return false;
}

// Read the actual header and perform more checks
size = gen_header._header_size;
_header = (GenericCDSFileMapHeader*)NEW_C_HEAP_ARRAY(char, size, mtInternal);
lseek(fd, 0, SEEK_SET);
n = os::read(fd, (void*)_header, (unsigned int)size);
if (n != size) {
FileMapInfo::fail_continue("Unable to read actual CDS file map header from shared archive");
return false;
}

if (!check_crc()) {
return false;
}

if (!check_and_init_base_archive_name()) {
return false;
}

// All fields in the GenericCDSFileMapHeader has been validated.
_is_valid = true;
return true;
}

GenericCDSFileMapHeader* get_generic_file_header() {
return &_header;
}

char* read_base_archive_name() {
assert(_fd != -1, "Archive should be open");
size_t name_size = _header._base_archive_name_size;
assert(name_size != 0, "For non-default base archive, name size should be non-zero!");
char* base_name = NEW_C_HEAP_ARRAY(char, name_size, mtInternal);
lseek(_fd, _header._base_archive_name_offset, SEEK_SET); // position to correct offset.
size_t n = os::read(_fd, base_name, (unsigned int)name_size);
if (n != name_size) {
log_info(cds)("Unable to read base archive name from archive");
FREE_C_HEAP_ARRAY(char, base_name);
return nullptr;
assert(_header != nullptr && _is_valid, "must be a valid archive file");
return _header;
}

const char* base_archive_name() {
assert(_header != nullptr && _is_valid, "must be a valid archive file");
return _base_archive_name;
}

private:
bool check_crc() {
if (VerifySharedSpaces) {
FileMapHeader* header = (FileMapHeader*)_header;
int actual_crc = header->compute_crc();
if (actual_crc != header->crc()) {
log_info(cds)("_crc expected: %d", header->crc());
log_info(cds)(" actual: %d", actual_crc);
FileMapInfo::fail_continue("Header checksum verification failed.");
return false;
}
}
if (base_name[name_size - 1] != '\0' || strlen(base_name) != name_size - 1) {
log_info(cds)("Base archive name is damaged");
FREE_C_HEAP_ARRAY(char, base_name);
return nullptr;
return true;
}

bool check_and_init_base_archive_name() {
unsigned int name_offset = _header->_base_archive_name_offset;
unsigned int name_size = _header->_base_archive_name_size;
unsigned int header_size = _header->_header_size;

if (name_offset + name_size < name_offset) {
FileMapInfo::fail_continue("base_archive_name offset/size overflow: " UINT32_FORMAT "/" UINT32_FORMAT,
name_offset, name_size);
return false;
}
if (!os::file_exists(base_name)) {
log_info(cds)("Base archive %s does not exist", base_name);
FREE_C_HEAP_ARRAY(char, base_name);
return nullptr;
if (_header->_magic == CDS_ARCHIVE_MAGIC) {
if (name_offset != 0) {
FileMapInfo::fail_continue("static shared archive must have zero _base_archive_name_offset");
return false;
}
if (name_size != 0) {
FileMapInfo::fail_continue("static shared archive must have zero _base_archive_name_size");
return false;
}
} else {
assert(_header->_magic == CDS_DYNAMIC_ARCHIVE_MAGIC, "must be");
if ((name_size == 0 && name_offset != 0) ||
(name_size != 0 && name_offset == 0)) {
// If either is zero, both must be zero. This indicates that we are using the default base archive.
FileMapInfo::fail_continue("Invalid base_archive_name offset/size: " UINT32_FORMAT "/" UINT32_FORMAT,
name_offset, name_size);
return false;
}
if (name_size > 0) {
if (name_offset + name_size > header_size) {
FileMapInfo::fail_continue("Invalid base_archive_name offset/size (out of range): "
UINT32_FORMAT " + " UINT32_FORMAT " > " UINT32_FORMAT ,
name_offset, name_size, header_size);
return false;
}
const char* name = ((const char*)_header) + _header->_base_archive_name_offset;
if (name[name_size - 1] != '\0' || strlen(name) != name_size - 1) {
FileMapInfo::fail_continue("Base archive name is damaged");
return false;
}
if (!os::file_exists(name)) {
FileMapInfo::fail_continue("Base archive %s does not exist", name);
return false;
}
_base_archive_name = name;
}
}
return base_name;
return true;
}
};

bool FileMapInfo::check_archive(const char* archive_name, bool is_static) {
FileHeaderHelper file_helper;
if (!file_helper.initialize(archive_name)) {
// do not vm_exit_during_initialization here because Arguments::init_shared_archive_paths()
// requires a shared archive name. The open_for_read() function will log a message regarding
// failure in opening a shared archive.
// Any errors are reported by fail_continue().
return false;
}

GenericCDSFileMapHeader* header = file_helper.get_generic_file_header();
if (is_static) {
if (header->_magic != CDS_ARCHIVE_MAGIC) {
vm_exit_during_initialization("Not a base shared archive", archive_name);
return false;
}
if (header->_base_archive_name_offset != 0) {
log_info(cds)("_base_archive_name_offset should be 0");
log_info(cds)("_base_archive_name_offset = " UINT32_FORMAT, header->_base_archive_name_offset);
fail_continue("Not a base shared archive: %s", archive_name);
return false;
}
} else {
if (header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) {
vm_exit_during_initialization("Not a top shared archive", archive_name);
fail_continue("Not a top shared archive: %s", archive_name);
return false;
}
unsigned int name_size = header->_base_archive_name_size;
unsigned int name_offset = header->_base_archive_name_offset;
unsigned int header_size = header->_header_size;
if (name_offset + name_size != header_size) {
log_info(cds)("_header_size should be equal to _base_archive_name_offset plus _base_archive_name_size");
log_info(cds)(" _base_archive_name_size = " UINT32_FORMAT, name_size);
log_info(cds)(" _base_archive_name_offset = " UINT32_FORMAT, name_offset);
log_info(cds)(" _header_size = " UINT32_FORMAT, header_size);
return false;
}
char* base_name = file_helper.read_base_archive_name();
if (base_name == nullptr) {
return false;
}
FREE_C_HEAP_ARRAY(char, base_name);
}
return true;
}

// Return value:
// false:
// <archive_name> is not a valid archive. *base_archive_name is set to null.
// true && (*base_archive_name) == NULL:
// <archive_name> is a valid static archive.
// true && (*base_archive_name) != NULL:
// <archive_name> is a valid dynamic archive.
bool FileMapInfo::get_base_archive_name_from_header(const char* archive_name,
char** base_archive_name) {
FileHeaderHelper file_helper;
*base_archive_name = NULL;

if (!file_helper.initialize(archive_name)) {
return false;
}
GenericCDSFileMapHeader* header = file_helper.get_generic_file_header();
if (header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) {
// Not a dynamic header, no need to proceed further.
return false;
assert(header->_magic == CDS_ARCHIVE_MAGIC, "must be");
return true;
}

if ((header->_base_archive_name_size == 0 && header->_base_archive_name_offset != 0) ||
(header->_base_archive_name_size != 0 && header->_base_archive_name_offset == 0)) {
fail_continue("Default base archive not set correct");
return false;
}
if (header->_base_archive_name_size == 0 &&
header->_base_archive_name_offset == 0) {
const char* base = file_helper.base_archive_name();
if (base == nullptr) {
*base_archive_name = Arguments::get_default_shared_archive_path();
} else {
// read the base archive name
*base_archive_name = file_helper.read_base_archive_name();
if (*base_archive_name == nullptr) {
return false;
}
*base_archive_name = os::strdup_check_oom(base);
}

return true;
}

Expand Down Expand Up @@ -1247,16 +1321,6 @@ bool FileMapInfo::init_from_file(int fd) {
return false;
}

if (VerifySharedSpaces) {
int expected_crc = header()->compute_crc();
if (expected_crc != header()->crc()) {
log_info(cds)("_crc expected: %d", expected_crc);
log_info(cds)(" actual: %d", header()->crc());
FileMapInfo::fail_continue("Header checksum verification failed.");
return false;
}
}

_file_offset = header()->header_size(); // accounts for the size of _base_archive_name

if (is_static()) {
Expand Down Expand Up @@ -1294,9 +1358,9 @@ bool FileMapInfo::open_for_read() {
int fd = os::open(_full_path, O_RDONLY | O_BINARY, 0);
if (fd < 0) {
if (errno == ENOENT) {
fail_continue("Specified shared archive not found (%s).", _full_path);
fail_continue("Specified shared archive not found (%s)", _full_path);
} else {
fail_continue("Failed to open shared archive file (%s).",
fail_continue("Failed to open shared archive file (%s)",
os::strerror(errno));
}
return false;
Expand Down
4 changes: 3 additions & 1 deletion src/hotspot/share/include/cds.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#define NUM_CDS_REGIONS 7 // this must be the same as MetaspaceShared::n_regions
#define CDS_ARCHIVE_MAGIC 0xf00baba2
#define CDS_DYNAMIC_ARCHIVE_MAGIC 0xf00baba8
#define CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION 12
#define CURRENT_CDS_ARCHIVE_VERSION 12

typedef struct CDSFileMapRegion {
Expand All @@ -59,7 +60,8 @@ typedef struct CDSFileMapRegion {
char* _mapped_base; // Actually mapped address (NULL if this region is not mapped).
} CDSFileMapRegion;

// This portion of the archive file header must remain unchanged for _version >= 12.
// This portion of the archive file header must remain unchanged for
// _version >= CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION (12).
// This makes it possible to read important information from a CDS archive created by
// a different version of HotSpot, so that we can automatically regenerate the archive as necessary.
typedef struct GenericCDSFileMapHeader {
Expand Down
Loading

1 comment on commit 3e93e0b

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.