Index: lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.h =================================================================== --- lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.h +++ lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.h @@ -22,6 +22,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" // C includes @@ -40,10 +41,11 @@ lldb::offset_t GetByteSize(); - llvm::Optional> - GetStream(MinidumpStreamType stream_type); + llvm::ArrayRef GetStream(MinidumpStreamType stream_type); - llvm::Optional> GetThreads(); + llvm::Optional GetMinidumpString(uint32_t rva); + + llvm::ArrayRef GetThreads(); const MinidumpSystemInfo *GetSystemInfo(); @@ -51,15 +53,22 @@ const MinidumpMiscInfo *GetMiscInfo(); + llvm::Optional GetLinuxProcStatus(); + + llvm::Optional GetPid(); + + llvm::ArrayRef GetModuleList(); + + const MinidumpExceptionStream *GetExceptionStream(); + private: lldb::DataBufferSP m_data_sp; const MinidumpHeader *m_header; llvm::DenseMap m_directory_map; - MinidumpParser(const lldb::DataBufferSP &data_buf_sp, - const MinidumpHeader *header, - const llvm::DenseMap - &directory_map); + MinidumpParser( + const lldb::DataBufferSP &data_buf_sp, const MinidumpHeader *header, + llvm::DenseMap &&directory_map); }; } // namespace minidump Index: lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.cpp =================================================================== --- lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.cpp +++ lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.cpp @@ -1,5 +1,4 @@ -//===-- MinidumpParser.cpp ---------------------------------------*- C++ -//-*-===// +//===-- MinidumpParser.cpp ---------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // @@ -56,13 +55,12 @@ directory->location; } - MinidumpParser parser(data_buf_sp, header, directory_map); - return llvm::Optional(parser); + return MinidumpParser(data_buf_sp, header, std::move(directory_map)); } MinidumpParser::MinidumpParser( const lldb::DataBufferSP &data_buf_sp, const MinidumpHeader *header, - const llvm::DenseMap &directory_map) + llvm::DenseMap &&directory_map) : m_data_sp(data_buf_sp), m_header(header), m_directory_map(directory_map) { } @@ -70,50 +68,48 @@ return m_data_sp->GetByteSize(); } -llvm::Optional> +llvm::ArrayRef MinidumpParser::GetStream(MinidumpStreamType stream_type) { auto iter = m_directory_map.find(static_cast(stream_type)); if (iter == m_directory_map.end()) - return llvm::None; + return {}; // check if there is enough data if (iter->second.rva + iter->second.data_size > m_data_sp->GetByteSize()) - return llvm::None; + return {}; + + return llvm::ArrayRef(m_data_sp->GetBytes() + iter->second.rva, + iter->second.data_size); +} - llvm::ArrayRef arr_ref(m_data_sp->GetBytes() + iter->second.rva, - iter->second.data_size); - return llvm::Optional>(arr_ref); +llvm::Optional MinidumpParser::GetMinidumpString(uint32_t rva) { + auto arr_ref = m_data_sp->GetData(); + if (rva > arr_ref.size()) + return llvm::None; + arr_ref = arr_ref.drop_front(rva); + return parseMinidumpString(arr_ref); } -llvm::Optional> -MinidumpParser::GetThreads() { - llvm::Optional> data = - GetStream(MinidumpStreamType::ThreadList); +llvm::ArrayRef MinidumpParser::GetThreads() { + llvm::ArrayRef data = GetStream(MinidumpStreamType::ThreadList); - if (!data) + if (data.size() == 0) return llvm::None; - return MinidumpThread::ParseThreadList(data.getValue()); + return MinidumpThread::ParseThreadList(data); } const MinidumpSystemInfo *MinidumpParser::GetSystemInfo() { - llvm::Optional> data = - GetStream(MinidumpStreamType::SystemInfo); + llvm::ArrayRef data = GetStream(MinidumpStreamType::SystemInfo); - if (!data) + if (data.size() == 0) return nullptr; - return MinidumpSystemInfo::Parse(data.getValue()); + return MinidumpSystemInfo::Parse(data); } ArchSpec MinidumpParser::GetArchitecture() { ArchSpec arch_spec; - arch_spec.GetTriple().setOS(llvm::Triple::OSType::UnknownOS); - arch_spec.GetTriple().setVendor(llvm::Triple::VendorType::UnknownVendor); - arch_spec.GetTriple().setArch(llvm::Triple::ArchType::UnknownArch); - - // TODO should we add the OS type here, or somewhere else ? - const MinidumpSystemInfo *system_info = GetSystemInfo(); if (!system_info) @@ -122,35 +118,110 @@ // TODO what to do about big endiand flavors of arm ? // TODO set the arm subarch stuff if the minidump has info about it + llvm::Triple triple; + triple.setVendor(llvm::Triple::VendorType::UnknownVendor); + const MinidumpCPUArchitecture arch = static_cast( static_cast(system_info->processor_arch)); + switch (arch) { case MinidumpCPUArchitecture::X86: - arch_spec.GetTriple().setArch(llvm::Triple::ArchType::x86); + triple.setArch(llvm::Triple::ArchType::x86); break; case MinidumpCPUArchitecture::AMD64: - arch_spec.GetTriple().setArch(llvm::Triple::ArchType::x86_64); + triple.setArch(llvm::Triple::ArchType::x86_64); break; case MinidumpCPUArchitecture::ARM: - arch_spec.GetTriple().setArch(llvm::Triple::ArchType::arm); + triple.setArch(llvm::Triple::ArchType::arm); break; case MinidumpCPUArchitecture::ARM64: - arch_spec.GetTriple().setArch(llvm::Triple::ArchType::aarch64); + triple.setArch(llvm::Triple::ArchType::aarch64); + break; + default: + triple.setArch(llvm::Triple::ArchType::UnknownArch); break; default: break; } + const MinidumpOSPlatform os = static_cast( + static_cast(system_info->platform_id)); + + // TODO add all of the OSes that Minidump/breakpad distinguishes? + switch (os) { + case MinidumpOSPlatform::Win32S: + case MinidumpOSPlatform::Win32Windows: + case MinidumpOSPlatform::Win32NT: + case MinidumpOSPlatform::Win32CE: + triple.setOS(llvm::Triple::OSType::Win32); + break; + case MinidumpOSPlatform::Linux: + triple.setOS(llvm::Triple::OSType::Linux); + break; + case MinidumpOSPlatform::MacOSX: + triple.setOS(llvm::Triple::OSType::MacOSX); + break; + case MinidumpOSPlatform::Android: + triple.setOS(llvm::Triple::OSType::Linux); + triple.setEnvironment(llvm::Triple::EnvironmentType::Android); + break; + default: + triple.setOS(llvm::Triple::OSType::UnknownOS); + break; + } + + arch_spec.SetTriple(triple); + return arch_spec; } const MinidumpMiscInfo *MinidumpParser::GetMiscInfo() { - llvm::Optional> data = - GetStream(MinidumpStreamType::MiscInfo); + llvm::ArrayRef data = GetStream(MinidumpStreamType::MiscInfo); + + if (data.size() == 0) + return nullptr; + + return MinidumpMiscInfo::Parse(data); +} + +llvm::Optional MinidumpParser::GetLinuxProcStatus() { + llvm::ArrayRef data = GetStream(MinidumpStreamType::LinuxProcStatus); + + if (data.size() == 0) + return llvm::None; + + return LinuxProcStatus::Parse(data); +} + +llvm::Optional MinidumpParser::GetPid() { + const MinidumpMiscInfo *misc_info = GetMiscInfo(); + if (misc_info != nullptr) { + return misc_info->GetPid(); + } + + llvm::Optional proc_status = GetLinuxProcStatus(); + if (proc_status.hasValue()) { + return proc_status->GetPid(); + } + + return llvm::None; +} + +llvm::ArrayRef MinidumpParser::GetModuleList() { + llvm::ArrayRef data = GetStream(MinidumpStreamType::ModuleList); + + if (data.size() == 0) + return {}; + + return MinidumpModule::ParseModuleList(data); +} + +const MinidumpExceptionStream *MinidumpParser::GetExceptionStream() { + llvm::ArrayRef data = GetStream(MinidumpStreamType::Exception); - if (!data) + if (data.size() == 0) return nullptr; - return MinidumpMiscInfo::Parse(data.getValue()); + return MinidumpExceptionStream::Parse(data); } Index: lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.h =================================================================== --- lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.h +++ lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.h @@ -18,6 +18,9 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/BitmaskEnum.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Endian.h" // C includes @@ -148,6 +151,12 @@ LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ IDIVT) }; +enum class MinidumpMiscInfoFlags : uint32_t { + ProcessID = (1 << 0), + ProcessTimes = (1 << 1), + LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ ProcessTimes) +}; + template Error consumeObject(llvm::ArrayRef &Buffer, const T *&Object) { Error error; @@ -161,6 +170,11 @@ return error; } +// parse a MinidumpString which is with UTF-16 +// Reference: +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680395(v=vs.85).aspx +llvm::Optional parseMinidumpString(llvm::ArrayRef &data); + // Reference: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680378(v=vs.85).aspx struct MinidumpHeader { @@ -219,7 +233,7 @@ static const MinidumpThread *Parse(llvm::ArrayRef &data); - static llvm::Optional> + static llvm::ArrayRef ParseThreadList(llvm::ArrayRef &data); }; static_assert(sizeof(MinidumpThread) == 48, @@ -272,12 +286,12 @@ static_assert(sizeof(MinidumpSystemInfo) == 56, "sizeof MinidumpSystemInfo is not correct!"); -// TODO check flags to see what's valid // TODO misc2, misc3 ? // Reference: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680389(v=vs.85).aspx struct MinidumpMiscInfo { llvm::support::ulittle32_t size; + // flags1 represents what info in the struct is valid llvm::support::ulittle32_t flags1; llvm::support::ulittle32_t process_id; llvm::support::ulittle32_t process_create_time; @@ -285,10 +299,94 @@ llvm::support::ulittle32_t process_kernel_time; static const MinidumpMiscInfo *Parse(llvm::ArrayRef &data); + + llvm::Optional GetPid() const; }; static_assert(sizeof(MinidumpMiscInfo) == 24, "sizeof MinidumpMiscInfo is not correct!"); +// The /proc/pid/status is saved as an ascii string in the file +class LinuxProcStatus { +public: + llvm::StringRef proc_status; + lldb::pid_t pid; + + static llvm::Optional Parse(llvm::ArrayRef &data); + + lldb::pid_t GetPid() const; + +private: + LinuxProcStatus() = default; +}; + +// MinidumpModule stuff +struct MinidumpVSFixedFileInfo { + llvm::support::ulittle32_t signature; + llvm::support::ulittle32_t struct_version; + llvm::support::ulittle32_t file_version_hi; + llvm::support::ulittle32_t file_version_lo; + llvm::support::ulittle32_t product_version_hi; + llvm::support::ulittle32_t product_version_lo; + // file_flags_mask - identifies valid bits in fileFlags + llvm::support::ulittle32_t file_flags_mask; + llvm::support::ulittle32_t file_flags; + llvm::support::ulittle32_t file_os; + llvm::support::ulittle32_t file_type; + llvm::support::ulittle32_t file_subtype; + llvm::support::ulittle32_t file_date_hi; + llvm::support::ulittle32_t file_date_lo; +}; +static_assert(sizeof(MinidumpVSFixedFileInfo) == 52, + "sizeof MinidumpVSFixedFileInfo is not correct!"); + +struct MinidumpModule { + llvm::support::ulittle64_t base_of_image; + llvm::support::ulittle32_t size_of_image; + llvm::support::ulittle32_t checksum; + llvm::support::ulittle32_t time_date_stamp; + llvm::support::ulittle32_t module_name_rva; + MinidumpVSFixedFileInfo version_info; + MinidumpLocationDescriptor CV_record; + MinidumpLocationDescriptor misc_record; + llvm::support::ulittle32_t reserved0[2]; + llvm::support::ulittle32_t reserved1[2]; + + static const MinidumpModule *Parse(llvm::ArrayRef &data); + + static llvm::ArrayRef + ParseModuleList(llvm::ArrayRef &data); +}; +static_assert(sizeof(MinidumpModule) == 108, + "sizeof MinidumpVSFixedFileInfo is not correct!"); + +// Exception stuff +struct MinidumpException { + enum { + MaxParams = 15, + }; + + llvm::support::ulittle32_t exception_code; + llvm::support::ulittle32_t exception_flags; + llvm::support::ulittle64_t exception_record; + llvm::support::ulittle64_t exception_address; + llvm::support::ulittle32_t number_parameters; + llvm::support::ulittle32_t unused_alignment; + llvm::support::ulittle64_t exception_information[MaxParams]; +}; +static_assert(sizeof(MinidumpException) == 152, + "sizeof MinidumpException is not correct!"); + +struct MinidumpExceptionStream { + llvm::support::ulittle32_t thread_id; + llvm::support::ulittle32_t alignment; + MinidumpException exception_record; + MinidumpLocationDescriptor thread_context; + + static const MinidumpExceptionStream *Parse(llvm::ArrayRef &data); +}; +static_assert(sizeof(MinidumpExceptionStream) == 168, + "sizeof MinidumpExceptionStream is not correct!"); + } // namespace minidump } // namespace lldb_private #endif // liblldb_MinidumpTypes_h_ Index: lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.cpp =================================================================== --- lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.cpp +++ lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.cpp @@ -9,7 +9,6 @@ // Project includes #include "MinidumpTypes.h" -#include "MinidumpParser.h" // Other libraries and framework includes // C includes @@ -40,6 +39,35 @@ return header; } +// Minidump string +llvm::Optional +lldb_private::minidump::parseMinidumpString(llvm::ArrayRef &data) { + std::string result; + + const uint32_t *source_length; + Error error = consumeObject(data, source_length); + if (error.Fail() || *source_length > data.size() || *source_length % 2 != 0) + return llvm::None; + + auto source_start = reinterpret_cast(data.data()); + // source_length is the length of the string in bytes + // we need the length of the string in UTF-16 characters/code points (16 bits + // per char) + // that's why it's divided by 2 + const auto source_end = source_start + (*source_length) / 2; + // resize to worst case length + result.resize(UNI_MAX_UTF8_BYTES_PER_CODE_POINT * (*source_length) / 2); + auto result_start = reinterpret_cast(&result[0]); + const auto result_end = result_start + result.size(); + ConvertUTF16toUTF8(&source_start, source_end, &result_start, result_end, + strictConversion); + const auto result_size = + std::distance(reinterpret_cast(&result[0]), result_start); + result.resize(result_size); // shrink to actual length + + return result; +} + // MinidumpThread const MinidumpThread *MinidumpThread::Parse(llvm::ArrayRef &data) { const MinidumpThread *thread = nullptr; @@ -50,24 +78,15 @@ return thread; } -llvm::Optional> +llvm::ArrayRef MinidumpThread::ParseThreadList(llvm::ArrayRef &data) { - std::vector thread_list; - const llvm::support::ulittle32_t *thread_count; Error error = consumeObject(data, thread_count); - if (error.Fail()) - return llvm::None; + if (error.Fail() || *thread_count * sizeof(MinidumpThread) > data.size()) + return {}; - const MinidumpThread *thread; - for (uint32_t i = 0; i < *thread_count; ++i) { - thread = MinidumpThread::Parse(data); - if (thread == nullptr) - return llvm::None; - thread_list.push_back(thread); - } - - return llvm::Optional>(thread_list); + return llvm::ArrayRef( + reinterpret_cast(data.data()), *thread_count); } // MinidumpSystemInfo @@ -90,3 +109,70 @@ return misc_info; } + +llvm::Optional MinidumpMiscInfo::GetPid() const { + uint32_t pid_flag = + static_cast(MinidumpMiscInfoFlags::ProcessID); + if (flags1 & pid_flag) + return llvm::Optional(process_id); + + return llvm::None; +} + +// Linux Proc Status +// it's stored as an ascii string in the file +llvm::Optional +LinuxProcStatus::Parse(llvm::ArrayRef &data) { + LinuxProcStatus result; + result.proc_status = + llvm::StringRef(reinterpret_cast(data.data()), data.size()); + data = data.drop_front(data.size()); + + llvm::SmallVector lines; + result.proc_status.split(lines, '\n', 42); + // /proc/$pid/status has 41 lines, but why not use 42? + for (auto line : lines) { + if (line.consume_front("Pid:")) { + line = line.trim(); + if (!line.getAsInteger(10, result.pid)) + return result; + } + } + + return llvm::None; +} + +lldb::pid_t LinuxProcStatus::GetPid() const { return pid; } + +// Module stuff +const MinidumpModule *MinidumpModule::Parse(llvm::ArrayRef &data) { + const MinidumpModule *module = nullptr; + Error error = consumeObject(data, module); + if (error.Fail()) + return nullptr; + + return module; +} + +llvm::ArrayRef +MinidumpModule::ParseModuleList(llvm::ArrayRef &data) { + + const llvm::support::ulittle32_t *modules_count; + Error error = consumeObject(data, modules_count); + if (error.Fail() || *modules_count * sizeof(MinidumpModule) > data.size()) + return {}; + + return llvm::ArrayRef( + reinterpret_cast(data.data()), *modules_count); +} + +// Exception stuff +const MinidumpExceptionStream * +MinidumpExceptionStream::Parse(llvm::ArrayRef &data) { + const MinidumpExceptionStream *exception_stream = nullptr; + Error error = consumeObject(data, exception_stream); + if (error.Fail()) + return nullptr; + + return exception_stream; +} Index: lldb/trunk/unittests/Process/minidump/CMakeLists.txt =================================================================== --- lldb/trunk/unittests/Process/minidump/CMakeLists.txt +++ lldb/trunk/unittests/Process/minidump/CMakeLists.txt @@ -3,6 +3,7 @@ ) set(test_inputs - linux-x86_64.dmp) + linux-x86_64.dmp + fizzbuzz_no_heap.dmp) add_unittest_inputs(LLDBMinidumpTests "${test_inputs}") Index: lldb/trunk/unittests/Process/minidump/MinidumpParserTest.cpp =================================================================== --- lldb/trunk/unittests/Process/minidump/MinidumpParserTest.cpp +++ lldb/trunk/unittests/Process/minidump/MinidumpParserTest.cpp @@ -59,33 +59,111 @@ TEST_F(MinidumpParserTest, GetThreads) { SetUpData("linux-x86_64.dmp"); - llvm::Optional> thread_list; + llvm::ArrayRef thread_list; thread_list = parser->GetThreads(); - ASSERT_TRUE(thread_list.hasValue()); - ASSERT_EQ(1UL, thread_list->size()); + ASSERT_EQ(1UL, thread_list.size()); - const MinidumpThread *thread = thread_list.getValue()[0]; - ASSERT_EQ(16001UL, thread->thread_id); + const MinidumpThread thread = thread_list[0]; + ASSERT_EQ(16001UL, thread.thread_id); } TEST_F(MinidumpParserTest, GetThreadsTruncatedFile) { SetUpData("linux-x86_64.dmp", 200); - llvm::Optional> thread_list; + llvm::ArrayRef thread_list; thread_list = parser->GetThreads(); - ASSERT_FALSE(thread_list.hasValue()); + ASSERT_EQ(0UL, thread_list.size()); } TEST_F(MinidumpParserTest, GetArchitecture) { SetUpData("linux-x86_64.dmp"); ASSERT_EQ(llvm::Triple::ArchType::x86_64, - parser->GetArchitecture().GetTriple().getArch()); + parser->GetArchitecture().GetMachine()); + ASSERT_EQ(llvm::Triple::OSType::Linux, + parser->GetArchitecture().GetTriple().getOS()); } TEST_F(MinidumpParserTest, GetMiscInfo) { SetUpData("linux-x86_64.dmp"); const MinidumpMiscInfo *misc_info = parser->GetMiscInfo(); ASSERT_EQ(nullptr, misc_info); - // linux breakpad generated minidump files don't have misc info stream +} + +TEST_F(MinidumpParserTest, GetLinuxProcStatus) { + SetUpData("linux-x86_64.dmp"); + llvm::Optional proc_status = parser->GetLinuxProcStatus(); + ASSERT_TRUE(proc_status.hasValue()); + lldb::pid_t pid = proc_status->GetPid(); + ASSERT_EQ(16001UL, pid); +} + +TEST_F(MinidumpParserTest, GetPid) { + SetUpData("linux-x86_64.dmp"); + llvm::Optional pid = parser->GetPid(); + ASSERT_TRUE(pid.hasValue()); + ASSERT_EQ(16001UL, pid.getValue()); +} + +TEST_F(MinidumpParserTest, GetModuleList) { + SetUpData("linux-x86_64.dmp"); + llvm::ArrayRef modules = parser->GetModuleList(); + ASSERT_EQ(8UL, modules.size()); + std::string module_names[8] = { + "/usr/local/google/home/dvlahovski/projects/test_breakpad/a.out", + "/lib/x86_64-linux-gnu/libm-2.19.so", + "/lib/x86_64-linux-gnu/libc-2.19.so", + "/lib/x86_64-linux-gnu/libgcc_s.so.1", + "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19", + "/lib/x86_64-linux-gnu/libpthread-2.19.so", + "/lib/x86_64-linux-gnu/ld-2.19.so", + "linux-gate.so", + }; + + for (int i = 0; i < 8; ++i) { + llvm::Optional name = + parser->GetMinidumpString(modules[i].module_name_rva); + ASSERT_TRUE(name.hasValue()); + ASSERT_EQ(module_names[i], name.getValue()); + } +} + +TEST_F(MinidumpParserTest, GetExceptionStream) { + SetUpData("linux-x86_64.dmp"); + const MinidumpExceptionStream *exception_stream = + parser->GetExceptionStream(); + ASSERT_NE(nullptr, exception_stream); + ASSERT_EQ(11UL, exception_stream->exception_record.exception_code); +} + +// Windows Minidump tests +// fizzbuzz_no_heap.dmp is copied from the WinMiniDump tests +TEST_F(MinidumpParserTest, GetArchitectureWindows) { + SetUpData("fizzbuzz_no_heap.dmp"); + ASSERT_EQ(llvm::Triple::ArchType::x86, + parser->GetArchitecture().GetMachine()); + ASSERT_EQ(llvm::Triple::OSType::Win32, + parser->GetArchitecture().GetTriple().getOS()); +} + +TEST_F(MinidumpParserTest, GetLinuxProcStatusWindows) { + SetUpData("fizzbuzz_no_heap.dmp"); + llvm::Optional proc_status = parser->GetLinuxProcStatus(); + ASSERT_FALSE(proc_status.hasValue()); +} + +TEST_F(MinidumpParserTest, GetMiscInfoWindows) { + SetUpData("fizzbuzz_no_heap.dmp"); + const MinidumpMiscInfo *misc_info = parser->GetMiscInfo(); + ASSERT_NE(nullptr, misc_info); + llvm::Optional pid = misc_info->GetPid(); + ASSERT_TRUE(pid.hasValue()); + ASSERT_EQ(4440UL, pid.getValue()); +} + +TEST_F(MinidumpParserTest, GetPidWindows) { + SetUpData("fizzbuzz_no_heap.dmp"); + llvm::Optional pid = parser->GetPid(); + ASSERT_TRUE(pid.hasValue()); + ASSERT_EQ(4440UL, pid.getValue()); }