articles
articles copied to clipboard
005-chromium mojo调用方代码定位
本文主要是我在调试chromium
代码中,遇到不知道如何找到mojo
调用方代码所做的测试,主要以NetworkService
为案例,其他mojo
也相同
本文所有源码均来源于chromium公开的开源代码
案例
在调试过程中有如下日志输出
[19984:17852:1102/101508.140:INFO:cert_verifier.cc(85)] chromium(STACK): CreateDefaultBacktrace:
base::debug::CollectStackTrace [0x00007FFDA9A06FB0+48] (G:\codes\chrome_79\src\base\debug\stack_trace_win.cc:284)
base::debug::StackTrace::StackTrace [0x00007FFDA9A06120+80] (G:\codes\chrome_79\src\base\debug\stack_trace.cc:206)
base::debug::StackTrace::StackTrace [0x00007FFDA9A060A8+40] (G:\codes\chrome_79\src\base\debug\stack_trace.cc:203)
// 代码定位
net::CertVerifier::CreateDefault [0x00007FFDA88A82A4+68] (G:\codes\chrome_79\src\net\cert\cert_verifier.cc:85)
network::NetworkContext::MakeURLRequestContext [0x00007FFD78B05403+291] (G:\codes\chrome_79\src\services\network\network_context.cc:1591)
network::NetworkContext::NetworkContext [0x00007FFD78B04512+1330] (G:\codes\chrome_79\src\services\network\network_context.cc:350)
std::__1::make_unique<network::NetworkContext,network::NetworkService *,mojo::PendingReceiver<network::mojom::NetworkContext>,mojo::StructPtr<network::mojom::NetworkContextParams>,base::OnceCallback<void (network::NetworkContext *)> > [0x00007FFD78B778BF+255] (G:\codes\chrome_79\src\buildtools\third_party\libc++\trunk\include\memory:3131)
network::NetworkService::CreateNetworkContext [0x00007FFD78B776C3+403] (G:\codes\chrome_79\src\services\network\network_service.cc:407)
network::mojom::NetworkServiceStubDispatch::Accept [0x00007FFD78EAE6B4+4372] (G:\codes\chrome_79\src\out\Release\gen\services\network\public\mojom\network_service.mojom.cc:2412)
network::mojom::NetworkServiceStub<mojo::RawPtrImplRefTraits<network::mojom::NetworkService> >::Accept [0x00007FFD78B7F9B2+98] (G:\codes\chrome_79\src\out\Release\gen\services\network\public\mojom\network_service.mojom.h:399)
mojo::InterfaceEndpointClient::HandleValidatedMessage [0x00007FFDB4D57786+1862] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\interface_endpoint_client.cc:553)
mojo::InterfaceEndpointClient::HandleIncomingMessageThunk::Accept [0x00007FFDB4D57031+33] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\interface_endpoint_client.cc:140)
mojo::MessageDispatcher::Accept [0x00007FFDB4D67F68+344] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\message_dispatcher.cc:41)
mojo::InterfaceEndpointClient::HandleIncomingMessage [0x00007FFDB4D5A598+216] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\interface_endpoint_client.cc:356)
mojo::internal::MultiplexRouter::ProcessIncomingMessage [0x00007FFDB4D6DB2F+1663] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\multiplex_router.cc:876)
mojo::internal::MultiplexRouter::Accept [0x00007FFDB4D6D081+673] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\multiplex_router.cc:598)
mojo::MessageDispatcher::Accept [0x00007FFDB4D67F68+344] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\message_dispatcher.cc:41)
mojo::Connector::DispatchMessageW [0x00007FFDB4D3EB34+1396] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\connector.cc:604)
mojo::Connector::ReadAllAvailableMessages [0x00007FFDB4D403C3+675] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\connector.cc:680)
mojo::Connector::OnHandleReadyInternal [0x00007FFDB4D3FEDD+381] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\connector.cc:515)
mojo::Connector::OnWatcherHandleReady [0x00007FFDB4D3FD4B+27] (G:\codes\chrome_79\src\mojo\public\cpp\bindings\lib\connector.cc:475)
base::internal::FunctorTraits<void (mojo::Connector::*)(unsigned int),void>::Invoke<void (mojo::Connector::*)(unsigned int),mojo::Connector *,unsigned int> [0x00007FFDB4D487A5+69] (G:\codes\chrome_79\src\base\bind_internal.h:498)
base::internal::InvokeHelper<0,void>::MakeItSo<void (mojo::Connector::*const &)(unsigned int),mojo::Connector *,unsigned int> [0x00007FFDB4D486DD+77] (G:\codes\chrome_79\src\base\bind_internal.h:598)
base::internal::Invoker<base::internal::BindState<void (mojo::Connector::*)(unsigned int),base::internal::UnretainedWrapper<mojo::Connector> >,void (unsigned int)>::RunImpl<void (mojo::Connector::*const &)(unsigned int),const std::__1::tuple<base::interna [0x00007FFDB4D48671+113] (G:\codes\chrome_79\src\base\bind_internal.h:671)
base::internal::Invoker<base::internal::BindState<void (mojo::Connector::*)(unsigned int),base::internal::UnretainedWrapper<mojo::Connector> >,void (unsigned int)>::Run [0x00007FFDB4D48565+101] (G:\codes\chrome_79\src\base\bind_internal.h:653)
base::RepeatingCallback<void (unsigned int)>::Run [0x00007FFDB4D36DF8+104] (G:\codes\chrome_79\src\base\callback.h:132)
...l
base::Thread::Run [0x00007FFDA9C854AD+381] (G:\codes\chrome_79\src\base\threading\thread.cc:304)
base::Thread::ThreadMain [0x00007FFDA9C85B4F+1599] (G:\codes\chrome_79\src\base\threading\thread.cc:375)
base::`anonymous namespace'::ThreadFunc [0x00007FFDA9C7250B+379] (G:\codes\chrome_79\src\base\threading\platform_thread_win.cc:104)
BaseThreadInitThunk [0x00007FFDE1C27344+20]
RtlUserThreadStart [0x00007FFDE1F826B1+33]
现在想找到CreateDefault
具体的调用者位置,根据上面的日志可以发现CreateDefault
是在"网络线程"中被调用,具体由谁发起的,这是我们需要关心的
net::CertVerifier::CreateDefault [0x00007FFDA88A82A4+68] (G:\codes\chrome_79\src\net\cert\cert_verifier.cc:85)
network::NetworkContext::MakeURLRequestContext [0x00007FFD78B05403+291] (G:\codes\chrome_79\src\services\network\network_context.cc:1591)
network::NetworkContext::NetworkContext [0x00007FFD78B04512+1330] (G:\codes\chrome_79\src\services\network\network_context.cc:350)
std::__1::make_unique<network::NetworkContext,network::NetworkService *,mojo::PendingReceiver<network::mojom::NetworkContext>,mojo::StructPtr<network::mojom::NetworkContextParams>,base::OnceCallback<void (network::NetworkContext *)> > [0x00007FFD78B778BF+255] (G:\codes\chrome_79\src\buildtools\third_party\libc++\trunk\include\memory:3131)
network::NetworkService::CreateNetworkContext
根据日志找到如下mojom
定义
struct LoadInfo {
uint32 process_id;
uint32 routing_id;
string host;
uint32 load_state; // net::LoadState enum
mojo_base.mojom.String16 state_param;
uint64 upload_position;
uint64 upload_size;
};
// Network service interface to the browser.
// network接口发送到browser接口
interface NetworkServiceClient {
// Called periodically to update the client about progress of the current
// loads. To avoid flooding the client, it has to ack the update before it can
// receive the next update.
OnLoadingStateUpdate(array<LoadInfo> infos) => ();
// Called to send raw header information and information about excluded
// cookies. Only called when |devtool_request_id| is available to the
// URLLoader.
OnRawRequest(
int32 process_id,
int32 routing_id,
string devtool_request_id,
array<CookieWithStatus> cookies_with_status,
array<HttpRawHeaderPair> headers);
// Called to send information about the cookies blocked from storage from a
// received response. Only called when |devtool_request_id| is available to
// the URLLoader.
OnRawResponse(
int32 process_id,
int32 routing_id,
string devtool_request_id,
array<CookieAndLineWithStatus> cookies_with_status,
array<HttpRawHeaderPair> headers,
string? raw_response_headers);
};
// ...
// Parameters needed to initialize the network service.
struct NetworkServiceParams {
ConnectionType initial_connection_type = CONNECTION_UNKNOWN;
ConnectionSubtype initial_connection_subtype = SUBTYPE_UNKNOWN;
// A set of environment variables that should be set in the network
// service when starting up.
array<EnvironmentVariable> environment;
};
// Information about how logging should be configured.
// Corresponds to logging::LoggingSettings.
[EnableIf=is_chromeos]
struct LoggingSettings {
uint32 logging_dest;
handle log_file_descriptor;
};
// Browser interface to the network service.
// browser接口发送数据到network接口
interface NetworkService {
// Sets client used by all |NetworkContext|s creating by |NetworkService|.
// Pending requests may hang if the |client| pipe is closed before they
// complete.
SetClient(pending_remote<NetworkServiceClient> client,
NetworkServiceParams params);
// Reinitializes the Network Service's logging with the given settings. This
// is needed on Chrome OS, which switches to a log file in the user's home
// directory once they log in.
[EnableIf=is_chromeos]
ReinitializeLogging(LoggingSettings settings);
// Starts logging SSL key material to the |file|. This must be called before
// any SSL connections are made. (See |SSLClientSocket::SetSSLKeyLogger()|
// for more details).
SetSSLKeyLogFile(mojo_base.mojom.File file);
// Creates a new network context with the given parameters.
CreateNetworkContext(pending_receiver<NetworkContext> context,
NetworkContextParams params);
//...
// Binds the test service's testing interface. Available only in some test
// environments.
BindTestInterface(pending_receiver<NetworkServiceTest> receiver);
};
在mojom
中定义了两个接口
- NetworkServiceClient: 实现:通常在浏览器进程中实现。它定义了网络服务可以调用的回调,以通知浏览器进程网络事件或状态的变化。 调用:当网络服务需要通知浏览器进程某些事件(例如,网络状态变化或其他通知)时,网络服务会调用这个接口的方法。这些调用通常发生在网络服务进程的线程中。
- NetworkService: 实现:在网络服务进程中实现。它定义了浏览器进程可以调用的网络操作或查询。 调用:当浏览器进程需要执行网络操作或查询时(例如,发起一个新的网络请求或获取网络状态),它会调用这个接口的方法。这些调用通常发生在浏览器进程的主线程或网络线程中。
可以看一下mojom
生成后的文件
src\out\Release\gen\services\network\public\mojom\network_service.mojom.h
src\out\Release\gen\services\network\public\mojom\network_service.mojom.cc
这两个生成的文件最主要是定义了8个结构
-
NetworkServiceClient (Interface):
- 这是基于
.mojom
文件定义的接口,描述了客户端应该实现的方法集合。 - 作用:为实现该接口的对象提供合约或蓝图。
- 这是基于
-
NetworkServiceClientProxy:
- 代表在其他进程中的
NetworkServiceClient
接口的实例。当你调用其上的方法时,它会将这些调用打包成mojo
消息并发送到其他进程。 - 作用:作为发送者,将方法调用转换为
mojo
消息并发送它们。
-
NetworkServiceClientStub:
- 这个类处理传入的
mojo
消息并将它们分派给实现NetworkServiceClient
接口的对象。 - 作用:作为接收器,接收从对端发送的消息并调用相应的方法。
- 这个类处理传入的
-
NetworkServiceStubDispatch:
- 它为
NetworkServiceStub
类提供支持。 - 当
NetworkServiceStub
接收到一个mojo
消息时,它会使用NetworkServiceStubDispatch
中的方法来确定如何处理该消息。这同样涉及确定要调用哪个 NetworkService 接口的方法,并进行相应的参数解码。
- 它为
- 与
NetworkServiceClientStubDispatch
一样,这个类也通常包含静态方法,这些方法知道如何为每个在.mojom
文件中定义的方法处理消息。
-
NetworkService (Interface):
- 这是基于
.mojom
文件定义的接口,描述了网络服务应该实现的方法集合。 - 作用:为实现该接口的对象提供合约或蓝图。
- 这是基于
-
NetworkServiceProxy:
- 代表在其他进程中的
NetworkService
接口的实例。当你调用其上的方法时,它会将这些调用打包成mojo
消息并发送到其他进程。 - 作用:作为发送者,将方法调用转换为
mojo
消息并发送它们。
- 代表在其他进程中的
-
NetworkServiceStub:
- 这个类处理传入的
mojo
消息并将它们分派给实现NetworkService
接口的对象。 - 作用:作为接收器,接收从对端发送的消息并调用相应的方法。
- 这个类处理传入的
-
NetworkServiceClientStubDispatch:
- 它为
NetworkServiceClientStub
类提供支持。 - 当
NetworkServiceClientStub
接收到一个mojo
消息时,它会使用NetworkServiceClientStubDispatch
中的方法来确定如何处理该消息。这包括确定要调用哪个NetworkServiceClient
接口的方法,并将消息的参数解码为正确的格式。 - 这个类通常包含静态方法,这些方法知道如何为每个在
.mojom
文件中定义的方法处理消息。
- 它为
简而言之:
-
Interface 类(例如
NetworkServiceClient
和NetworkService
)定义了方法的集合,这些方法可以通过IPC
进行远程调用。 - Proxy 类处理从一个进程到另一个进程的消息发送。
- Stub 类处理从另一个进程到这个进程的消息接收。
-
StubDispatch 类型的类起到了一个关键的中介角色,帮助正确地处理和分派从另一个进程或线程接收的
mojo
消息。
这种结构允许两个运行在不同进程中的对象通过mojo IPC
机制相互通信。
对mojo
生成的代码有了基础的了解之后再来看一下开始的日志信息
// CreateDefault被调用
net::CertVerifier::CreateDefault
network::NetworkContext::MakeURLRequestContext
network::NetworkContext::NetworkContext
std::__1::make_unique<network::NetworkContext,network::NetworkService *,mojo::PendingReceiver<network::mojom::NetworkContext>,mojo::StructPtr<network::mojom::NetworkContextParams>,base::OnceCallback<void (network::NetworkContext *)> >
// CreateDefault通过CreateNetworkContext创建
network::NetworkService::CreateNetworkContext
// CreateNetworkContext通过NetworkServiceStubDispatch派发
network::mojom::NetworkServiceStubDispatch::Accept
上文中说明了NetworkServiceProxy
的作用,其相当于在其他进程中NetworkService
的代理,所以理论上来说,我们只要在proxy
代理这部分获取调用栈,应该就能获取到对应使用该功能的代码
注意STACK
设置位置是生成的mojo
实现代码中,在out
目录类似于这样的位置
src\out\Release\gen\services\network\public\mojom\network_service.mojom.cc
设置
void NetworkServiceProxy::CreateNetworkContext(
mojo::PendingReceiver<::network::mojom::NetworkContext> in_context,
::network::mojom::NetworkContextParamsPtr in_params) {
// 获取调用栈
GM_STACK();
#if BUILDFLAG(MOJO_TRACE_ENABLED)
TRACE_EVENT0(
"mojom",
"<class "
"'jinja2::utils::Namespace'>::NetworkService::CreateNetworkContext");
#endif
const bool kExpectsResponse = false;
const bool kIsSync = false;
const uint32_t kFlags =
((kExpectsResponse) ? mojo::Message::kFlagExpectsResponse : 0) |
((kIsSync) ? mojo::Message::kFlagIsSync : 0);
// ...
}
启动chrome
,并查看调用栈
[27448:9696:1107/101016.170:INFO:network_service.mojom.cc(1115)] chromium(STACK): CreateNetworkContextBacktrace:
base::debug::CollectStackTrace [0x00007FFDD7B46FB0+48] (G:\codes\chrome_79\src\base\debug\stack_trace_win.cc:284)
base::debug::StackTrace::StackTrace [0x00007FFDD7B46120+80] (G:\codes\chrome_79\src\base\debug\stack_trace.cc:206)
base::debug::StackTrace::StackTrace [0x00007FFDD7B460A8+40] (G:\codes\chrome_79\src\base\debug\stack_trace.cc:203)
network::mojom::NetworkServiceProxy::CreateNetworkContext [0x00007FFD96F6F13F+79] (G:\codes\chrome_79\src\out\Release\gen\services\network\public\mojom\network_service.mojom.cc:1115)
safe_browsing::SafeBrowsingNetworkContext::SharedURLLoaderFactory::GetNetworkContext [0x00007FFD9E474F7D+365] (G:\codes\chrome_79\src\components\safe_browsing\browser\safe_browsing_network_context.cc:52)
safe_browsing::SafeBrowsingNetworkContext::SharedURLLoaderFactory::GetURLLoaderFactory [0x00007FFD9E47A4AD+333] (G:\codes\chrome_79\src\components\safe_browsing\browser\safe_browsing_network_context.cc:103)
safe_browsing::SafeBrowsingNetworkContext::SharedURLLoaderFactory::CreateLoaderAndStart [0x00007FFD9E479FF9+313] (G:\codes\chrome_79\src\components\safe_browsing\browser\safe_browsing_network_context.cc:79)
network::`anonymous namespace'::SimpleURLLoaderImpl::StartRequest [0x00007FFDCD917D2C+1276] (G:\codes\chrome_79\src\services\network\public\cpp\simple_url_loader.cc:1523)
network::`anonymous namespace'::SimpleURLLoaderImpl::Start [0x00007FFDCD911EEA+922] (G:\codes\chrome_79\src\services\network\public\cpp\simple_url_loader.cc:1500)
network::`anonymous namespace'::SimpleURLLoaderImpl::DownloadToStringOfUnboundedSizeUntilCrashAndDie [0x00007FFDCD90CE65+197] (G:\codes\chrome_79\src\services\network\public\cpp\simple_url_loader.cc:1197)
safe_browsing::ModelLoader::StartFetch [0x00007FFDA3BA6AA9+553] (G:\codes\chrome_79\src\chrome\browser\safe_browsing\client_side_model_loader.cc:162)
...
--------------------------
[27448:9696:1107/101006.114:INFO:network_service.mojom.cc(1115)] chromium(STACK): CreateNetworkContextBacktrace:
base::debug::CollectStackTrace [0x00007FFDD7B46FB0+48] (G:\codes\chrome_79\src\base\debug\stack_trace_win.cc:284)
base::debug::StackTrace::StackTrace [0x00007FFDD7B46120+80] (G:\codes\chrome_79\src\base\debug\stack_trace.cc:206)
base::debug::StackTrace::StackTrace [0x00007FFDD7B460A8+40] (G:\codes\chrome_79\src\base\debug\stack_trace.cc:203)
network::mojom::NetworkServiceProxy::CreateNetworkContext [0x00007FFD96F6F13F+79] (G:\codes\chrome_79\src\out\Release\gen\services\network\public\mojom\network_service.mojom.cc:1115)
ProfileNetworkContextService::CreateNetworkContext [0x00007FFDA0448E02+210] (G:\codes\chrome_79\src\chrome\browser\net\profile_network_context_service.cc:230)
Profile::CreateNetworkContext [0x00007FFD9F5699E1+81] (G:\codes\chrome_79\src\chrome\browser\profiles\profile.cc:288)
ChromeContentBrowserClient::CreateNetworkContext [0x00007FFD9F51CA0B+123] (G:\codes\chrome_79\src\chrome\browser\chrome_content_browser_client.cc:4679)
content::StoragePartitionImpl::InitNetworkContext [0x00007FFD99A00243+131] (G:\codes\chrome_79\src\content\browser\storage_partition_impl.cc:2281)
content::StoragePartitionImpl::GetNetworkContext [0x00007FFD99A0017F+207] (G:\codes\chrome_79\src\content\browser\storage_partition_impl.cc:1345)
content::URLLoaderFactoryGetter::HandleNetworkFactoryRequestOnUIThread [0x00007FFD99A9783C+460] (G:\codes\chrome_79\src\content\browser\url_loader_factory_getter.cc:299)
content::URLLoaderFactoryGetter::Initialize [0x00007FFD99A9759A+362] (G:\codes\chrome_79\src\content\browser\url_loader_factory_getter.cc:144)
content::StoragePartitionImpl::Initialize [0x00007FFD999FE4A6+3766] (G:\codes\chrome_79\src\content\browser\storage_partition_impl.cc:1285)
content::StoragePartitionImplMap::Get [0x00007FFD99A3DFF4+548] (G:\codes\chrome_79\src\content\browser\storage_partition_impl_map.cc:357)
content::`anonymous namespace'::GetStoragePartitionFromConfig [0x00007FFD984D3713+147] (G:\codes\chrome_79\src\content\browser\browser_context.cc:171)
content::BrowserContext::GetStoragePartition [0x00007FFD984D363C+252] (G:\codes\chrome_79\src\content\browser\browser_context.cc:388)
content::BrowserContext::GetDefaultStoragePartition [0x00007FFD984D38AD+29] (G:\codes\chrome_79\src\content\browser\browser_context.cc:420)
ProfileImpl::DoFinalInit [0x00007FFDA048D061+2177] (G:\codes\chrome_79\src\chrome\browser\profiles\profile_impl.cc:702)
ProfileImpl::OnLocaleReady [0x00007FFDA048F5D8+1192] (G:\codes\chrome_79\src\chrome\browser\profiles\profile_impl.cc:987)
ProfileImpl::OnPrefsLoaded [0x00007FFDA048BEE6+566] (G:\codes\chrome_79\src\chrome\browser\profiles\profile_impl.cc:1018)
ProfileImpl::ProfileImpl [0x00007FFDA048A5B4+1924] (G:\codes\chrome_79\src\chrome\browser\profiles\profile_impl.cc:540)
Profile::CreateProfile [0x00007FFDA04897F7+1111] (G:\codes\chrome_79\src\chrome\browser\profiles\profile_impl.cc:376)
ProfileManager::CreateProfileHelper [0x00007FFD9F58F0A5+341] (G:\codes\chrome_79\src\chrome\browser\profiles\profile_manager.cc:1164)
ProfileManager::CreateAndInitializeProfile [0x00007FFD9F587299+537] (G:\codes\chrome_79\src\chrome\browser\profiles\profile_manager.cc:1398)
ProfileManager::GetProfile [0x00007FFD9F5867C2+386] (G:\codes\chrome_79\src\chrome\browser\profiles\profile_manager.cc:504)
GetStartupProfile [0x00007FFDA1592D71+113] (G:\codes\chrome_79\src\chrome\browser\ui\startup\startup_browser_creator.cc:1041)
`anonymous namespace'::CreatePrimaryProfile [0x00007FFDA0F2C103+947] (G:\codes\chrome_79\src\chrome\browser\chrome_browser_main.cc:425)
ChromeBrowserMainParts::PreMainMessageLoopRunImpl [0x00007FFDA0F2A419+3609] (G:\codes\chrome_79\src\chrome\browser\chrome_browser_main.cc:1521)
ChromeBrowserMainParts::PreMainMessageLoopRun [0x00007FFDA0F29553+307] (G:\codes\chrome_79\src\chrome\browser\chrome_browser_main.cc:1195)
content::BrowserMainLoop::PreMainMessageLoopRun [0x00007FFD98538C8B+363] (G:\codes\chrome_79\src\content\browser\browser_main_loop.cc:1009)
base::internal::FunctorTraits<int (content::BrowserMainLoop::*)(),void>::Invoke<int (content::BrowserMainLoop::*)(),content::BrowserMainLoop *> [0x00007FFD9854152A+26] (G:\codes\chrome_79\src\base\bind_internal.h:498)
base::internal::InvokeHelper<0,int>::MakeItSo<int (content::BrowserMainLoop::*)(),content::BrowserMainLoop *> [0x00007FFD985414A4+52] (G:\codes\chrome_79\src\base\bind_internal.h:598)
base::internal::Invoker<base::internal::BindState<int (content::BrowserMainLoop::*)
类似的还有不少,可以根据自己的需要去找到真正调用该NetworkService
的功能代码。
上述文中用到的打印调用栈代码
#include "base/logging.h"
#include <base/debug/stack_trace.h>
base::debug::StackTrace st;
LOG(INFO) << "chromium(STACK): " << __FUNCTION__ << st.ToString()