// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_ZUCCHINI_EXCEPTION_FILTER_HELPER_WIN_H_
#define COMPONENTS_ZUCCHINI_EXCEPTION_FILTER_HELPER_WIN_H_
#include <windows.h>
#include <stddef.h>
#include <stdint.h>
#include <map>
#include "base/containers/span.h"
namespace zucchini {
// A helper for handling EXCEPTION_IN_PAGE_ERROR structured exceptions in
// multiple memory ranges; for example, when several files are mapped into the
// process's address space. Consumers must construct an instance outside of
// a `try-except` statement, call `AddRange` one or more times (possibly within
// the `__try` block), and call `FilterPageError` within the filter expression.
// For example:
// ```
// ExceptionFilterHelper exception_filter_helper;
// __try {
// base::MemoryMappedFile mapped_file;
// if (mapped_file.Initialize("/foo/bar")) {
// exception_filter_helper.AddRange(mapped_file.data(),
// mapped_file.length());
// ProcessFile(mapped_file);
// }
// } __except(exception_filter_helper.FilterPageError(
// GetExceptionInformation()->ExceptionRecord) {
// // I/O error accessing the mapped file.
// }
// ```
class ExceptionFilterHelper {
public:
ExceptionFilterHelper();
ExceptionFilterHelper(const ExceptionFilterHelper&) = delete;
ExceptionFilterHelper& operator=(const ExceptionFilterHelper&) = delete;
~ExceptionFilterHelper();
// Adds a memory range within which page errors are to be handled. `range`
// must not overlap with any previously-added range.
void AddRange(base::span<const uint8_t> range);
// Returns `EXCEPTION_EXECUTE_HANDLER` if `exception_record` corresponds to an
// `EXCEPTION_IN_PAGE_ERROR` for an address wthin a range of memory previously
// added via `AddRange`; otherwise, returns `EXCEPTION_CONTINUE_SEARCH`.
int FilterPageError(const EXCEPTION_RECORD* const exception_record);
// Returns the NTSTATUS of the most-recently handled exception for which
// `FilterPageError` returned `EXCEPTION_EXECUTE_HANDLER`.
int32_t nt_status() const { return nt_status_; }
// Returns `true` if the most-recently handled exception for which
// `FilterPageError` returned `EXCEPTION_EXECUTE_HANDLER` was caused by a
// write to a mapped region; otherwise, the exception was caused by a read.
bool is_write() const { return is_write_; }
private:
// Returns `true` if `address` is within any range added to the instance via
// `AddRange`.
bool IsInRange(const uint8_t* address) const;
// A mapping of start address to one-past-end-address for all ranges added to
// the instance.
std::map<uintptr_t, uintptr_t> ranges_;
// The NTSTATUS code of the most-recently handled exception.
int32_t nt_status_ = 0;
// True if the most-recently handled exception was caused by a write.
bool is_write_ = false;
};
} // namespace zucchini
#endif // COMPONENTS_ZUCCHINI_EXCEPTION_FILTER_HELPER_WIN_H_