summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/dynamicclass.cpp
blob: fc9924c09cfb424d84291f71673b59aa01323984 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
#include "dynamicclass.ipp"

#include <locale>
#include <new>
#include <sstream>


namespace util {

namespace detail {

/// \brief Complete object locator equivalent structure
///
/// Structure used for locating the complete object and type information
/// for the MSVC C++ ABI on 64-bit targets.  A pointer to this structure
/// appears immediately before the first virtual member function entry
/// in the virtual function table.
struct dynamic_derived_class_base::msvc_complete_object_locator_equiv
{
	unsigned long signature;        ///< Magic number, always 1
	unsigned long offset;           ///< Offset from complete object to current object
	unsigned long ctor_disp_offset; ///< For avoiding extra constructor virtual tables for virtual inheritance
	int type_info_offs;             ///< Offset to type info
	int class_info_offs;            ///< Offset to class hierarchy info
	int self_offs;                  ///< Offset to this structure for calculating module base
};


/// \brief Type info equivalent structure
///
/// Structure equivalent to the implementation of std::type_info for the
/// MSVC C++ ABI.  The structure is followed immediately by the
/// decorated name.  The pointer to the undecorated name is normally
/// populated lazily when the \c name member function is called.
struct dynamic_derived_class_base::msvc_type_info_equiv
{
	void const *vptr;           ///< Pointer to virtual table
	char const *undecorated;    ///< Pointer to the undecorated name
	char decorated[1];          ///< First character of the decorated name
};


/// \brief Construct dynamic derived class base
///
/// Creates type info for a class with a single base class using the
/// specified name.  If the name contains multiple components separated
/// by \c :: separators, it is interpreted as a nested class name.
/// \param [in] name The name for the dynamic class.  Components must
///   start with an alphabetic character or an underscore, and may
///   contain only alphanumeric characters and underscores.
/// \exception std::invalid_argument Thrown if the class name is invalid
///   or unsupported.
/// \exception std::bad_alloc Thrown if allocating memory for the type
///   info fails.
dynamic_derived_class_base::dynamic_derived_class_base(std::string_view name) :
	m_base_vtable(nullptr)
{
	assert(!reinterpret_cast<void *>(std::uintptr_t(static_cast<void (*)()>(nullptr))));
	assert(!reinterpret_cast<void (*)()>(std::uintptr_t(static_cast<void *>(nullptr))));

	std::locale const &clocale(std::locale::classic());
	if (name.empty() || (('_' != name[0]) && !std::isalpha(name[0], clocale)) || (std::find_if_not(name.begin(), name.end(), [&clocale] (char c) { return (':' == c) || ('_' == c) || std::isalnum(c, clocale); }) != name.end()))
		throw std::invalid_argument("Invalid class name");

	std::ostringstream str;
	str.imbue(clocale);

#if MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC
	str << "class " << name;
	m_name = std::move(str).str();
	str.str(std::string());

	str << ".?AV";
	std::string_view::size_type found = name.find(':');
	while (std::string_view::npos != found)
	{
		if (((found + 2) >= name.length()) || (name[found + 1] != ':') || (('_' != name[found + 2]) && !std::isalpha(name[found + 2], clocale)))
			throw std::invalid_argument("Invalid class name");

		str.write(&name[0], found);
		str << '@';
		name.remove_prefix(found + 2);
		found = name.find(':');
	}
	str << name << "@@";
	std::string const mangled = std::move(str).str();

	m_type_info = reinterpret_cast<msvc_type_info_equiv *>(
			operator new (
				offsetof(msvc_type_info_equiv, decorated) + mangled.length() + 1,
				std::align_val_t(alignof(msvc_type_info_equiv))));
	m_type_info->vptr = *reinterpret_cast<void const *const *>(&typeid(dynamic_derived_class_base));
	m_type_info->undecorated = m_name.c_str();
	std::copy_n(mangled.c_str(), mangled.length() + 1, m_type_info->decorated);
#else
	class base { };
	class derived : base { };

	m_type_info.vptr = *reinterpret_cast<void const *const *>(&typeid(derived));

	std::string_view::size_type found = name.find(':');
	bool const qualified = std::string::npos != found;
	if (qualified)
		str << 'N';
	while (std::string_view::npos != found)
	{
		if (((found + 2) >= name.length()) || (name[found + 1] != ':') || (('_' != name[found + 2]) && !std::isalpha(name[found + 2], clocale)))
			throw std::invalid_argument("Invalid class name");

		str << found;
		str.write(&name[0], found);
		name.remove_prefix(found + 2);
		found = name.find(':');
	}
	str << name.length() << name;
	if (qualified)
		str << 'E';
	m_name = std::move(str).str();

	m_type_info.name = m_name.c_str();
#endif
}


dynamic_derived_class_base::~dynamic_derived_class_base()
{
#if MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC
	operator delete (m_type_info, std::align_val_t(alignof(msvc_type_info_equiv)));
#endif
}


/// \brief Get virtual table index for member function
///
/// Gets the virtual table index represented by a pointer to a virtual
/// member function.  The \p slot argument must refer to a virtual
/// member function returning a supported type that does not require
/// \c this pointer adjustment.
/// \param [in] slot Internal representation of pointer to a virtual
///   member function.  May be modified.
/// \param [in] size Size of the member function pointer type for the
///   \p slot argument.
/// \return The virtual table index of the member function, in terms of
///   the size of a virtual member function in the virtual table.
/// \exception std::invalid_argument Thrown if the \p slot argument is
///   not a supported virtual member function.
std::size_t dynamic_derived_class_base::resolve_virtual_member_slot(
		member_function_pointer_equiv &slot,
		std::size_t size)
{
#if MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC
	if ((sizeof(msvc_mi_member_function_pointer_equiv) <= size) && slot.adj)
		throw std::invalid_argument("Member function requires this pointer adjustment");
#if defined(__x86_64__) || defined(_M_X64)
	std::uint8_t const *func = reinterpret_cast<std::uint8_t const *>(slot.ptr);
	while (0xe9 == func[0]) // relative jump with 32-bit displacement (typically a resolved PLT entry)
		func += std::ptrdiff_t(5) + *reinterpret_cast<std::int32_t const *>(func + 1);
	if ((0x48 == func[0]) && (0x8b == func[1]) && (0x01 == func[2]))
	{
		if ((0xff == func[3]) && ((0x20 == func[4]) || (0x60 == func[4]) || (0xa0 == func[4])))
		{
			// MSVC virtual function call thunk - mov rax,QWORD PTR [rcx] ; jmp QWORD PTR [rax+...]
			if (0x20 == func[4]) // no displacement
			{
				return 0;
			}
			else if (0x60 == func[4]) // 8-bit displacement
			{
				auto const index = *reinterpret_cast<std::int8_t const *>(func + 5);
				if (index % (sizeof(std::uintptr_t) * MEMBER_FUNCTION_SIZE))
					throw std::invalid_argument("Invalid member function virtual table index");
				return index / sizeof(std::uintptr_t) / MEMBER_FUNCTION_SIZE;
			}
			else // 32-bit displacement
			{
				auto const index = *reinterpret_cast<std::int32_t const *>(func + 5);
				if (index % (sizeof(std::uintptr_t) * MEMBER_FUNCTION_SIZE))
					throw std::invalid_argument("Invalid member function virtual table index");
				return index / sizeof(std::uintptr_t) / MEMBER_FUNCTION_SIZE;
			}
		}
		else if ((0x48 == func[3]) && (0x8b == func[4]))
		{
			// clang virtual function call thunk - mov rax,QWORD PTR [rcx] ; mov rax,QWORD PTR [rax+...] ; jmp rax
			if  ((0x00 == func[5]) && (0x48 == func[6]) && (0xff == func[7]) && (0xe0 == func[8]))
			{
				// no displacement
				return 0;
			}
			else if  ((0x40 == func[5]) && (0x48 == func[7]) && (0xff == func[8]) && (0xe0 == func[9]))
			{
				// 8-bit displacement
				auto const index = *reinterpret_cast<std::int8_t const *>(func + 6);
				if (index % (sizeof(std::uintptr_t) * MEMBER_FUNCTION_SIZE))
					throw std::invalid_argument("Invalid member function virtual table index");
				return index / sizeof(std::uintptr_t) / MEMBER_FUNCTION_SIZE;
			}
			else if ((0x80 == func[5]) && (0x48 == func[10]) && (0xff == func[11]) && (0xe0 == func[12]))
			{
				// 32-bit displacement
				auto const index = *reinterpret_cast<std::int32_t const *>(func + 6);
				if (index % (sizeof(std::uintptr_t) * MEMBER_FUNCTION_SIZE))
					throw std::invalid_argument("Invalid member function virtual table index");
				return index / sizeof(std::uintptr_t) / MEMBER_FUNCTION_SIZE;
			}
		}
	}
	throw std::invalid_argument("Not a supported pointer to a virtual member function");
#else
	throw std::runtime_error("Unsupported architecture");
#endif
#else
	if (!slot.is_virtual())
		throw std::invalid_argument("Not a pointer to a virtual member function");
	if (slot.this_pointer_offset())
		throw std::invalid_argument("Member function requires this pointer adjustment");
	if (MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_STANDARD)
		slot.ptr -= 1;
	if (slot.ptr % (sizeof(std::uintptr_t) * MEMBER_FUNCTION_SIZE))
		throw std::invalid_argument("Invalid member function virtual table index");
	return slot.ptr / sizeof(std::uintptr_t) / MEMBER_FUNCTION_SIZE;
#endif
}

} // namespace detail

} // namespace util